diff options
217 files changed, 4068 insertions, 1205 deletions
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java index 4cf46e5364ea..f01ac0247908 100644 --- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java +++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java @@ -565,7 +565,6 @@ public class ByteBufferPerfTest { } @Test - @Parameters(method = "getData") public void time_new_byteArray() throws Exception { final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { @@ -574,7 +573,6 @@ public class ByteBufferPerfTest { } @Test - @Parameters(method = "getData") public void time_ByteBuffer_allocate() throws Exception { final BenchmarkState state = mBenchmarkRule.getState(); while (state.keepRunning()) { diff --git a/cmds/uinput/examples/test-touchpad.evemu b/cmds/uinput/examples/test-touchpad.evemu new file mode 100644 index 000000000000..34ee5721b630 --- /dev/null +++ b/cmds/uinput/examples/test-touchpad.evemu @@ -0,0 +1,44 @@ +# EVEMU 1.2 +# This is an evemu "recording" of an Apple Magic Trackpad (1st generation), but +# that doesn't actually make any movements. It just runs for a very long time, +# to make Android think a touchpad is connected. This is useful for testing +# things like the settings in System > Touchpad, which only appear when one is +# connected. +# +# It can be played by piping it to the uinput command over ADB: +# $ adb shell uinput - < test-touchpad.evemu +N: Fake touchpad +I: 0005 05ac 030e 0160 +P: 05 00 00 00 00 00 00 00 +B: 00 0b 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 01 00 00 00 00 00 +B: 01 20 e5 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 01 00 00 00 00 00 00 00 00 +B: 02 00 00 00 00 00 00 00 00 +B: 03 03 00 00 00 00 80 73 02 +B: 04 10 00 00 00 00 00 00 00 +B: 05 00 00 00 00 00 00 00 00 +B: 11 00 00 00 00 00 00 00 00 +B: 12 00 00 00 00 00 00 00 00 +A: 00 -2909 3167 4 0 46 +A: 01 -2456 2565 4 0 45 +A: 2f 0 15 0 0 0 +A: 30 0 1020 4 0 0 +A: 31 0 1020 4 0 0 +A: 34 -31 32 1 0 0 +A: 35 -2909 3167 4 0 46 +A: 36 -2456 2565 4 0 45 +A: 39 0 65535 0 0 0 +E: 0.000001 0004 0005 1234 +E: 0.000001 0000 0000 0000 +E: 1000000000.000000 0004 0005 1235 +E: 1000000000.000000 0000 0000 0000 diff --git a/core/api/current.txt b/core/api/current.txt index 56852212ab1e..5e8febebee0e 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -145,12 +145,12 @@ package android { field public static final String MANAGE_DEVICE_POLICY_AUDIO_OUTPUT = "android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT"; field public static final String MANAGE_DEVICE_POLICY_AUTOFILL = "android.permission.MANAGE_DEVICE_POLICY_AUTOFILL"; field public static final String MANAGE_DEVICE_POLICY_BACKUP_SERVICE = "android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE"; - field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = "android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"; + field public static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = "android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"; field public static final String MANAGE_DEVICE_POLICY_BLUETOOTH = "android.permission.MANAGE_DEVICE_POLICY_BLUETOOTH"; field public static final String MANAGE_DEVICE_POLICY_BUGREPORT = "android.permission.MANAGE_DEVICE_POLICY_BUGREPORT"; field public static final String MANAGE_DEVICE_POLICY_CALLS = "android.permission.MANAGE_DEVICE_POLICY_CALLS"; field public static final String MANAGE_DEVICE_POLICY_CAMERA = "android.permission.MANAGE_DEVICE_POLICY_CAMERA"; - field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"; + field public static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"; field public static final String MANAGE_DEVICE_POLICY_CERTIFICATES = "android.permission.MANAGE_DEVICE_POLICY_CERTIFICATES"; field public static final String MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE = "android.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE"; field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String MANAGE_DEVICE_POLICY_CONTENT_PROTECTION = "android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"; @@ -172,7 +172,7 @@ package android { field public static final String MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS = "android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"; field public static final String MANAGE_DEVICE_POLICY_METERED_DATA = "android.permission.MANAGE_DEVICE_POLICY_METERED_DATA"; field public static final String MANAGE_DEVICE_POLICY_MICROPHONE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE"; - field @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"; + field public static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = "android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"; field public static final String MANAGE_DEVICE_POLICY_MOBILE_NETWORK = "android.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK"; field public static final String MANAGE_DEVICE_POLICY_MODIFY_USERS = "android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"; field public static final String MANAGE_DEVICE_POLICY_MTE = "android.permission.MANAGE_DEVICE_POLICY_MTE"; @@ -8121,7 +8121,7 @@ package android.app.admin { method public boolean isLogoutEnabled(); method public boolean isManagedProfile(@NonNull android.content.ComponentName); method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName); - method @FlaggedApi("android.app.admin.flags.is_mte_policy_enforced") public static boolean isMtePolicyEnforced(); + method public static boolean isMtePolicyEnforced(); method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName); method public boolean isOrganizationOwnedDeviceWithManagedProfile(); method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName); @@ -8597,7 +8597,7 @@ package android.app.admin { field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452 field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451 field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455 - field @FlaggedApi("android.app.admin.flags.backup_service_security_log_event_enabled") public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c + field public static final int TAG_BACKUP_SERVICE_TOGGLED = 210044; // 0x3347c field public static final int TAG_BLUETOOTH_CONNECTION = 210039; // 0x33477 field public static final int TAG_BLUETOOTH_DISCONNECTION = 210040; // 0x33478 field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ba160372f51d..60dc52b655d6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1322,7 +1322,7 @@ package android.app.admin { method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public String getFinancedDeviceKioskRoleHolder(); - method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getMaxPolicyStorageLimit(); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getMaxPolicyStorageLimit(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle); @@ -1349,7 +1349,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING) public void setAuditLogEventCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.admin.SecurityLog.SecurityEvent>>); method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); - method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); + method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 42f761570849..4fc70769a3b1 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2003,6 +2003,7 @@ package android.media { method public void setRampingRingerEnabled(boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setRs2Value(float); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setVolumeControllerLongPressTimeoutEnabled(boolean); method @FlaggedApi("android.media.audio.focus_exclusive_with_recording") @RequiresPermission(android.Manifest.permission.QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull android.media.AudioAttributes); } diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 4c97dbbb4d01..4d61f418af10 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -106,6 +106,17 @@ flag { flag { namespace: "backstage_power" + name: "use_app_info_not_launched" + description: "Use the notLaunched state from ApplicationInfo instead of current value" + is_fixed_read_only: true + bug: "362516211" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "backstage_power" name: "cache_get_current_user_id" description: "Add caching for getCurrentUserId" is_fixed_read_only: true diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0f54cb7bc35e..8e97fe6b1c15 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -55,10 +55,8 @@ import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED; -import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import static android.app.admin.flags.Flags.onboardingConsentlessBugreports; -import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; @@ -4232,7 +4230,6 @@ public class DevicePolicyManager { * * @return whether MTE is currently enabled on the device. */ - @FlaggedApi(FLAG_IS_MTE_POLICY_ENFORCED) public static boolean isMtePolicyEnforced() { return Zygote.nativeSupportsMemoryTagging(); } @@ -12575,10 +12572,24 @@ public class DevicePolicyManager { **/ @SystemApi public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) { + setSecondaryLockscreenEnabled(admin, enabled, null); + } + + /** + * Called by the system supervision app to set whether a secondary lockscreen needs to be shown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the + * caller is not a device admin. + * @param enabled Whether or not the lockscreen needs to be shown. + * @param options A {@link PersistableBundle} to supply options to the lock screen. + * @hide + */ + public void setSecondaryLockscreenEnabled(@Nullable ComponentName admin, boolean enabled, + @Nullable PersistableBundle options) { throwIfParentInstance("setSecondaryLockscreenEnabled"); if (mService != null) { try { - mService.setSecondaryLockscreenEnabled(admin, enabled); + mService.setSecondaryLockscreenEnabled(admin, enabled, options); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -17746,7 +17757,6 @@ public class DevicePolicyManager { */ @SystemApi @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) - @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED) public void setMaxPolicyStorageLimit(int storageLimit) { if (mService != null) { try { @@ -17766,7 +17776,6 @@ public class DevicePolicyManager { */ @SystemApi @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) - @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED) public int getMaxPolicyStorageLimit() { if (mService != null) { try { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index d4e5c9960c2a..a4e2b8f62a23 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -303,7 +303,7 @@ interface IDevicePolicyManager { String[] getAccountTypesWithManagementDisabled(String callerPackageName); String[] getAccountTypesWithManagementDisabledAsUser(int userId, String callerPackageName, in boolean parent); - void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled); + void setSecondaryLockscreenEnabled(in ComponentName who, boolean enabled, in PersistableBundle options); boolean isSecondaryLockscreenEnabled(in UserHandle userHandle); void setPreferentialNetworkServiceConfigs( diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java index 477f2e007b33..beb93fd079d9 100644 --- a/core/java/android/app/admin/SecurityLog.java +++ b/core/java/android/app/admin/SecurityLog.java @@ -16,10 +16,7 @@ package android.app.admin; -import static android.app.admin.flags.Flags.FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED; - import android.Manifest; -import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -611,7 +608,6 @@ public class SecurityLog { * <li> [2] backup service state ({@code Integer}, 1 for enabled, 0 for disabled) * @see DevicePolicyManager#setBackupServiceEnabled(ComponentName, boolean) */ - @FlaggedApi(FLAG_BACKUP_SERVICE_SECURITY_LOG_EVENT_ENABLED) public static final int TAG_BACKUP_SERVICE_TOGGLED = SecurityLogTags.SECURITY_BACKUP_SERVICE_TOGGLED; /** diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 9ed5aa61b3e1..8e08a95dad70 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -13,6 +13,7 @@ flag { bug: "289520697" } +# Fully rolled out and must not be used. flag { name: "device_policy_size_tracking_enabled" is_exported: true @@ -22,13 +23,6 @@ flag { } flag { - name: "device_policy_size_tracking_internal_enabled" - namespace: "enterprise" - description: "Add feature to track the total policy size and have a max threshold - internal changes" - bug: "281543351" -} - -flag { name: "onboarding_bugreport_v2_enabled" is_exported: true namespace: "enterprise" @@ -44,13 +38,7 @@ flag { is_fixed_read_only: true } -flag { - name: "dedicated_device_control_enabled" - namespace: "enterprise" - description: "Allow the device management role holder to control which platform features are available on dedicated devices." - bug: "281964214" -} - +# Fully rolled out and must not be used. flag { name: "dedicated_device_control_api_enabled" is_exported: true @@ -170,6 +158,7 @@ flag { bug: "293441361" } +# Fully rolled out and must not be used. flag { name: "assist_content_user_restriction_enabled" is_exported: true @@ -188,6 +177,7 @@ flag { } } +# Fully rolled out and must not be used. flag { name: "backup_service_security_log_event_enabled" is_exported: true @@ -196,6 +186,7 @@ flag { bug: "304999634" } +# Fully rolled out and must not be used. flag { name: "esim_management_enabled" is_exported: true @@ -213,6 +204,7 @@ flag { bug: "289515470" } +# Fully rolled out and must not be used. flag { name: "is_mte_policy_enforced" is_exported: true diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index c4e8b4157752..c4bfae98e33d 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -72,14 +72,32 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { * we need to have per-package app function schemas. * * <p>This schema should be set visible to callers from the package owner itself and for callers - * with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link - * android.permission.EXECUTE_APP_FUNCTIONS} permissions. + * with {@link android.Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link + * android.Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permissions. * * @param packageName The package name to create a schema for. */ @NonNull public static AppSearchSchema createAppFunctionRuntimeSchema(@NonNull String packageName) { - return new AppSearchSchema.Builder(getRuntimeSchemaNameForPackage(packageName)) + return getAppFunctionRuntimeSchemaBuilder(getRuntimeSchemaNameForPackage(packageName)) + .addParentType(RUNTIME_SCHEMA_TYPE) + .build(); + } + + /** + * Creates a parent schema for all app function runtime schemas. + * + * <p>This schema should be set visible to the owner itself and for callers with {@link + * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@link + * android.permission.EXECUTE_APP_FUNCTIONS} permissions. + */ + public static AppSearchSchema createParentAppFunctionRuntimeSchema() { + return getAppFunctionRuntimeSchemaBuilder(RUNTIME_SCHEMA_TYPE).build(); + } + + private static AppSearchSchema.Builder getAppFunctionRuntimeSchemaBuilder( + @NonNull String schemaType) { + return new AppSearchSchema.Builder(schemaType) .addProperty( new AppSearchSchema.StringPropertyConfig.Builder(PROPERTY_FUNCTION_ID) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) @@ -111,9 +129,7 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { .setJoinableValueType( AppSearchSchema.StringPropertyConfig .JOINABLE_VALUE_TYPE_QUALIFIED_ID) - .build()) - .addParentType(RUNTIME_SCHEMA_TYPE) - .build(); + .build()); } /** Returns the function id. This might look like "com.example.message#send_message". */ diff --git a/core/java/android/app/jank/OWNERS b/core/java/android/app/jank/OWNERS new file mode 100644 index 000000000000..806de574b071 --- /dev/null +++ b/core/java/android/app/jank/OWNERS @@ -0,0 +1,4 @@ +steventerrell@google.com +carmenjackson@google.com +jjaggi@google.com +pmuetschard@google.com
\ No newline at end of file diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 297fe8a9e691..748260bc8d5f 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -98,11 +98,11 @@ flag { } flag { - name: "camera_multiple_input_streams" - is_exported: true - namespace: "virtual_devices" - description: "Expose multiple surface for the virtual camera owner for different stream resolution" - bug: "341083465" + name: "camera_multiple_input_streams" + is_exported: true + namespace: "virtual_devices" + description: "Expose multiple surface for the virtual camera owner for different stream resolution" + bug: "341083465" } flag { @@ -113,8 +113,16 @@ flag { } flag { - name: "status_bar_and_insets" - namespace: "virtual_devices" - description: "Allow for status bar and insets on virtual devices" - bug: "350007866" + namespace: "virtual_devices" + name: "display_power_manager_apis" + description: "Make relevant PowerManager APIs display aware by default" + bug: "365042486" + is_fixed_read_only: true +} + +flag { + name: "status_bar_and_insets" + namespace: "virtual_devices" + description: "Allow for status bar and insets on virtual devices" + bug: "350007866" } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 495ae6026805..34bea1a4df6f 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -849,6 +849,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5; + /** + * Whether the app has been previously not launched + * @hide + */ + public static final int PRIVATE_FLAG_EXT_NOT_LAUNCHED = 1 << 6; + /** @hide */ @IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = { PRIVATE_FLAG_EXT_PROFILEABLE, @@ -857,6 +863,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK, PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS, PRIVATE_FLAG_EXT_CPU_OVERRIDE, + PRIVATE_FLAG_EXT_NOT_LAUNCHED, }) @Retention(RetentionPolicy.SOURCE) public @interface ApplicationInfoPrivateFlagsExt {} @@ -2664,6 +2671,22 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** + * Returns whether the app in the STOPPED state. + * @hide + */ + public boolean isStopped() { + return (flags & ApplicationInfo.FLAG_STOPPED) != 0; + } + + /** + * Returns whether the app was never launched (any process started) before. + * @hide + */ + public boolean isNotLaunched() { + return (privateFlagsExt & ApplicationInfo.PRIVATE_FLAG_EXT_NOT_LAUNCHED) != 0; + } + + /** * Checks if a changeId is enabled for the current user * @param changeId The changeId to verify * @return True of the changeId is enabled diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 28534ad4516e..9eec7a4e8f71 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -426,4 +426,13 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + + +flag { + name: "caching_development_improvements" + namespace: "multiuser" + description: "System API to simplify caching implamentations" + bug: "364947162" + is_fixed_read_only: true +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 98904fe246f8..0a05f704f523 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5150,13 +5150,19 @@ public final class Settings { public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout"; /** - * The screen backlight brightness between 0 and 255. + * The screen backlight brightness between 1 (minimum) and 255 (maximum). + * + * Use {@link android.view.WindowManager.LayoutParams#screenBrightness} to set the screen + * brightness instead. */ @Readable public static final String SCREEN_BRIGHTNESS = "screen_brightness"; /** - * Control whether to enable automatic brightness mode. + * Controls whether to enable automatic brightness mode. Value can be set to + * {@link #SCREEN_BRIGHTNESS_MODE_MANUAL} or {@link #SCREEN_BRIGHTNESS_MODE_AUTOMATIC}. + * If {@link #SCREEN_BRIGHTNESS_MODE_AUTOMATIC} is set, the system may change + * {@link #SCREEN_BRIGHTNESS} automatically. */ @Readable public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode"; diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index 5d84d17bdb6e..bbe9cdbbce9f 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -35,20 +35,6 @@ public class ClientFlags { } /** - * @see Flags#phraseStrictFallback() - */ - public static boolean phraseStrictFallback() { - return TextFlags.isFeatureEnabled(Flags.FLAG_PHRASE_STRICT_FALLBACK); - } - - /** - * @see Flags#useBoundsForWidth() - */ - public static boolean useBoundsForWidth() { - return TextFlags.isFeatureEnabled(Flags.FLAG_USE_BOUNDS_FOR_WIDTH); - } - - /** * @see Flags#fixLineHeightForLocale() */ public static boolean fixLineHeightForLocale() { diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 9e02460d2637..0f1b031a8fad 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -56,8 +56,6 @@ public final class TextFlags { */ public static final String[] TEXT_ACONFIGS_FLAGS = { Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN, - Flags.FLAG_PHRASE_STRICT_FALLBACK, - Flags.FLAG_USE_BOUNDS_FOR_WIDTH, Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE, Flags.FLAG_ICU_BIDI_MIGRATION, Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU, @@ -70,8 +68,6 @@ public final class TextFlags { */ public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { Flags.noBreakNoHyphenationSpan(), - Flags.phraseStrictFallback(), - Flags.useBoundsForWidth(), Flags.fixLineHeightForLocale(), Flags.icuBidiMigration(), Flags.fixMisalignedContextMenu(), diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index d33c95e06677..b2be1a7ef351 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -2,14 +2,6 @@ package: "com.android.text.flags" container: "system" flag { - name: "vendor_custom_locale_fallback" - namespace: "text" - description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font." - is_fixed_read_only: true - bug: "278768958" -} - -flag { name: "new_fonts_fallback_xml" is_exported: true namespace: "text" @@ -20,13 +12,6 @@ flag { } flag { - name: "fix_double_underline" - namespace: "text" - description: "Feature flag for fixing double underline because of the multiple font used in the single line." - bug: "297336724" -} - -flag { name: "fix_line_height_for_locale" is_exported: true namespace: "text" @@ -66,13 +51,6 @@ flag { } flag { - name: "phrase_strict_fallback" - namespace: "text" - description: "Feature flag for automatic fallback from phrase based line break to strict line break." - bug: "281970875" -} - -flag { name: "use_bounds_for_width" is_exported: true namespace: "text" diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a4b28adae4a1..ef941da0e32d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1659,11 +1659,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (!hasUseBoundForWidthValue) { - if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) { - mUseBoundsForWidth = Flags.useBoundsForWidth(); - } else { - mUseBoundsForWidth = false; - } + mUseBoundsForWidth = CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH); } // TODO(b/179693024): Use a ChangeId instead. diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index cdac09796137..1709ca78af4b 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -404,6 +404,17 @@ public class RuntimeInit { } public static void redirectLogStreams$ravenwood() { + if (sOut$ravenwood != null && sErr$ravenwood != null) { + return; // Already initialized. + } + + // Make sure the Log class is loaded and the JNI methods are hooked up, + // before redirecting System.out/err. + // Otherwise, because ClassLoadHook tries to write to System.out, this would cause + // a circular initialization problem and would cause a UnsatisfiedLinkError + // on the JNI methods. + Log.isLoggable("X", Log.VERBOSE); + if (sOut$ravenwood == null) { sOut$ravenwood = System.out; System.setOut(new AndroidPrintStream(Log.INFO, "System.out")); diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 58818f35de22..4708be8108c2 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1144,7 +1144,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mDrawLegacyNavigationBarBackground = ((requestedVisibleTypes | mLastForceConsumingTypes) & WindowInsets.Type.navigationBars()) != 0 - && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0; + && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 + && navBarSize > 0; if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) { mDrawLegacyNavigationBarBackgroundHandled = mWindow.onDrawLegacyNavigationBarBackgroundChanged( diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 1fd933f29789..e440dc9053fd 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -34,7 +34,6 @@ import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Gro import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.INTERNED_DATA; import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_MESSAGE; @@ -72,7 +71,6 @@ import com.android.internal.protolog.common.LogLevel; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -103,6 +101,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private final ProtoLogDataSource mDataSource; @Nullable private final ProtoLogViewerConfigReader mViewerConfigReader; + @Deprecated @Nullable private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider; @NonNull @@ -148,6 +147,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto cacheUpdater, groups); } + @Deprecated @VisibleForTesting public PerfettoProtoLogImpl( @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @@ -160,6 +160,18 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto groups, dataSourceBuilder, configurationService); } + @VisibleForTesting + public PerfettoProtoLogImpl( + @Nullable String viewerConfigFilePath, + @Nullable ProtoLogViewerConfigReader viewerConfigReader, + @NonNull Runnable cacheUpdater, + @NonNull IProtoLogGroup[] groups, + @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, + @NonNull ProtoLogConfigurationService configurationService) { + this(viewerConfigFilePath, null, viewerConfigReader, cacheUpdater, + groups, dataSourceBuilder, configurationService); + } + private PerfettoProtoLogImpl( @Nullable String viewerConfigFilePath, @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @@ -449,6 +461,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto Log.d(LOG_TAG, "Finished onTracingFlush"); } + @Deprecated private void dumpViewerConfig() { if (mViewerConfigInputStreamProvider == null) { // No viewer config available @@ -457,103 +470,29 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto Log.d(LOG_TAG, "Dumping viewer config to trace"); - mDataSource.trace(ctx -> { - try { - ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream(); - - final ProtoOutputStream os = ctx.newTracePacket(); - - os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); - - final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG); - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - if (pis.getFieldNumber() == (int) MESSAGES) { - writeViewerConfigMessage(pis, os); - } - - if (pis.getFieldNumber() == (int) GROUPS) { - writeViewerConfigGroup(pis, os); - } - } - - os.end(outProtologViewerConfigToken); - } catch (IOException e) { - Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e); - } - }); + Utils.dumpViewerConfig(mDataSource, mViewerConfigInputStreamProvider); Log.d(LOG_TAG, "Dumped viewer config to trace"); } - private static void writeViewerConfigGroup( - ProtoInputStream pis, ProtoOutputStream os) throws IOException { - final long inGroupToken = pis.start(GROUPS); - final long outGroupToken = os.start(GROUPS); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) ID: - int id = pis.readInt(ID); - os.write(ID, id); - break; - case (int) NAME: - String name = pis.readString(NAME); - os.write(NAME, name); - break; - case (int) TAG: - String tag = pis.readString(TAG); - os.write(TAG, tag); - break; - default: - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inGroupToken); - os.end(outGroupToken); - } - - private static void writeViewerConfigMessage( - ProtoInputStream pis, ProtoOutputStream os) throws IOException { - final long inMessageToken = pis.start(MESSAGES); - final long outMessagesToken = os.start(MESSAGES); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MessageData.MESSAGE_ID: - os.write(MessageData.MESSAGE_ID, - pis.readLong(MessageData.MESSAGE_ID)); - break; - case (int) MESSAGE: - os.write(MESSAGE, pis.readString(MESSAGE)); - break; - case (int) LEVEL: - os.write(LEVEL, pis.readInt(LEVEL)); - break; - case (int) GROUP_ID: - os.write(GROUP_ID, pis.readInt(GROUP_ID)); - break; - case (int) LOCATION: - os.write(LOCATION, pis.readString(LOCATION)); - break; - default: - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inMessageToken); - os.end(outMessagesToken); - } - private void logToLogcat(String tag, LogLevel level, Message message, @Nullable Object[] args) { String messageString; if (mViewerConfigReader == null) { messageString = message.getMessage(); + + if (messageString == null) { + Log.e(LOG_TAG, "Failed to decode message for logcat. " + + "Message not available without ViewerConfig to decode the hash."); + } } else { messageString = message.getMessage(mViewerConfigReader); + + if (messageString == null) { + Log.e(LOG_TAG, "Failed to decode message for logcat. " + + "Message hash either not available in viewerConfig file or " + + "not loaded into memory from file before decoding."); + } } if (messageString == null) { @@ -760,7 +699,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto os.write(MessageData.MESSAGE_ID, messageHash); os.write(MESSAGE, message); - os.write(LEVEL, level.ordinal()); + os.write(LEVEL, level.id); os.write(GROUP_ID, logGroup.getId()); os.end(messageConfigToken); @@ -954,8 +893,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private static class Message { @Nullable private final Long mMessageHash; - @Nullable - private final Integer mMessageMask; + private final int mMessageMask; @Nullable private final String mMessageString; @@ -972,8 +910,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto this.mMessageString = messageString; } - @Nullable - private Integer getMessageMask() { + private int getMessageMask() { return mMessageMask; } diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java index d54a80b7c921..7031d694f09c 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java @@ -26,8 +26,6 @@ import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Mes import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; -import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG; -import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,7 +34,6 @@ import android.content.Context; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; -import android.os.SystemClock; import android.tracing.perfetto.DataSourceParams; import android.tracing.perfetto.InitArguments; import android.tracing.perfetto.Producer; @@ -125,7 +122,8 @@ public final class ProtoLogConfigurationService extends IProtoLogConfigurationSe this(ProtoLogDataSource::new, tracer); } - private ProtoLogConfigurationService( + @VisibleForTesting + public ProtoLogConfigurationService( @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, @NonNull ViewerConfigFileTracer tracer) { mDataSource = dataSourceBuilder.build( @@ -374,32 +372,13 @@ public final class ProtoLogConfigurationService extends IProtoLogConfigurationSe private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath) { - dataSource.trace(ctx -> { - final ProtoInputStream pis; + Utils.dumpViewerConfig(dataSource, () -> { try { - pis = new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); + return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); } catch (FileNotFoundException e) { throw new RuntimeException( "Failed to load viewer config file " + viewerConfigFilePath, e); } - - try { - final ProtoOutputStream os = ctx.newTracePacket(); - - os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); - - final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG); - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MESSAGES -> writeViewerConfigMessage(pis, os); - case (int) GROUPS -> writeViewerConfigGroup(pis, os); - } - } - - os.end(outProtologViewerConfigToken); - } catch (IOException e) { - Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e); - } }); } diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java index 1b2f5f7ccf2f..0afb135ac6d9 100644 --- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java +++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java @@ -283,10 +283,24 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance, public static class Instance extends DataSourceInstance { public interface TracingInstanceStartCallback { + /** + * Execute the tracing instance's onStart callback. + * @param instanceIdx The index of the tracing instance we are executing the callback + * for. + * @param config The protolog configuration for the tracing instance we are executing + * the callback for. + */ void run(int instanceIdx, @NonNull ProtoLogConfig config); } public interface TracingInstanceStopCallback { + /** + * Execute the tracing instance's onStop callback. + * @param instanceIdx The index of the tracing instance we are executing the callback + * for. + * @param config The protolog configuration for the tracing instance we are executing + * the callback for. + */ void run(int instanceIdx, @NonNull ProtoLogConfig config); } diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index da6d8cff6890..7bdcf2d14b19 100644 --- a/core/java/com/android/internal/protolog/ProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java @@ -23,6 +23,7 @@ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LO import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH; import android.annotation.Nullable; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.IProtoLog; @@ -37,6 +38,8 @@ import java.util.TreeMap; * A service for the ProtoLog logging system. */ public class ProtoLogImpl { + private static final String LOG_TAG = "ProtoLogImpl"; + private static IProtoLog sServiceInstance = null; @ProtoLogToolInjected(VIEWER_CONFIG_PATH) @@ -97,6 +100,9 @@ public class ProtoLogImpl { */ public static synchronized IProtoLog getSingleInstance() { if (sServiceInstance == null) { + Log.i(LOG_TAG, "Setting up " + ProtoLogImpl.class.getSimpleName() + " with " + + "viewerConfigPath = " + sViewerConfigPath); + final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]); if (android.tracing.Flags.perfettoProtologTracing()) { @@ -105,6 +111,9 @@ public class ProtoLogImpl { // TODO(b/353530422): Remove - temporary fix to unblock b/352290057 // In some tests the viewer config file might not exist in which we don't // want to provide config path to the user + Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up " + + ProtoLogImpl.class.getSimpleName() + ". " + + "Setting up without a viewer config instead..."); sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups); } else { sServiceInstance = diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java index 6c8996e610dc..0a80e006d5bc 100644 --- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java +++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java @@ -43,6 +43,13 @@ public class ProtoLogViewerConfigReader { return mLogMessageMap.get(messageHash); } + /** + * Load the viewer configs for the target groups into memory. + * Only viewer configs loaded into memory can be required. So this must be called for all groups + * we want to query before we query their viewer config. + * + * @param groups Groups to load the viewer configs from file into memory. + */ public synchronized void loadViewerConfig(@NonNull String[] groups) { loadViewerConfig(groups, (message) -> {}); } diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java new file mode 100644 index 000000000000..1e6ba309c046 --- /dev/null +++ b/core/java/com/android/internal/protolog/Utils.java @@ -0,0 +1,138 @@ +/* + * 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.internal.protolog; + +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; +import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.PROTOLOG_VIEWER_CONFIG; +import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP; + +import android.annotation.NonNull; +import android.internal.perfetto.protos.Protolog; +import android.os.SystemClock; +import android.util.Log; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; + +public class Utils { + private static final String LOG_TAG = "ProtoLogUtils"; + + /** + * Dump the viewer config provided by the input stream to the target datasource. + * @param dataSource The datasource to dump the ProtoLog viewer config to. + * @param viewerConfigInputStreamProvider The InputStream that provided the proto viewer config. + */ + public static void dumpViewerConfig(@NonNull ProtoLogDataSource dataSource, + @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) { + dataSource.trace(ctx -> { + try { + ProtoInputStream pis = viewerConfigInputStreamProvider.getInputStream(); + + final ProtoOutputStream os = ctx.newTracePacket(); + + os.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos()); + + final long outProtologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG); + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (pis.getFieldNumber() == (int) MESSAGES) { + writeViewerConfigMessage(pis, os); + } + + if (pis.getFieldNumber() == (int) GROUPS) { + writeViewerConfigGroup(pis, os); + } + } + + os.end(outProtologViewerConfigToken); + } catch (IOException e) { + Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump to datasource", e); + } + }); + } + + private static void writeViewerConfigGroup( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inGroupToken = pis.start(GROUPS); + final long outGroupToken = os.start(GROUPS); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) ID: + int id = pis.readInt(ID); + os.write(ID, id); + break; + case (int) NAME: + String name = pis.readString(NAME); + os.write(NAME, name); + break; + case (int) TAG: + String tag = pis.readString(TAG); + os.write(TAG, tag); + break; + default: + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inGroupToken); + os.end(outGroupToken); + } + + private static void writeViewerConfigMessage( + ProtoInputStream pis, ProtoOutputStream os) throws IOException { + final long inMessageToken = pis.start(MESSAGES); + final long outMessagesToken = os.start(MESSAGES); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID: + os.write(Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID, + pis.readLong(Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID)); + break; + case (int) MESSAGE: + os.write(MESSAGE, pis.readString(MESSAGE)); + break; + case (int) LEVEL: + os.write(LEVEL, pis.readInt(LEVEL)); + break; + case (int) GROUP_ID: + os.write(GROUP_ID, pis.readInt(GROUP_ID)); + break; + case (int) LOCATION: + os.write(LOCATION, pis.readString(LOCATION)); + break; + default: + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inMessageToken); + os.end(outMessagesToken); + } + +} diff --git a/core/java/com/android/internal/protolog/common/LogLevel.java b/core/java/com/android/internal/protolog/common/LogLevel.java index 16c34e1f333e..b5541ae81c2d 100644 --- a/core/java/com/android/internal/protolog/common/LogLevel.java +++ b/core/java/com/android/internal/protolog/common/LogLevel.java @@ -17,10 +17,18 @@ package com.android.internal.protolog.common; public enum LogLevel { - DEBUG("d"), VERBOSE("v"), INFO("i"), WARN("w"), ERROR("e"), WTF("wtf"); + DEBUG("d", 1), + VERBOSE("v", 2), + INFO("i", 3), + WARN("w", 4), + ERROR("e", 5), + WTF("wtf", 6); public final String shortCode; - LogLevel(String shortCode) { + public final int id; + + LogLevel(String shortCode, int id) { this.shortCode = shortCode; + this.id = id; } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a19b71c68deb..d35c66ed719e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4033,7 +4033,6 @@ <!-- Allows an application to manage policy related to block package uninstallation. <p>Protection level: internal|role <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only. - @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") --> <permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL" android:protectionLevel="internal|role" /> @@ -4041,7 +4040,6 @@ <!-- Allows an application to manage policy related to camera toggle. <p>Protection level: internal|role <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only. - @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") --> <permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE" android:protectionLevel="internal|role" /> @@ -4049,7 +4047,6 @@ <!-- Allows an application to manage policy related to microphone toggle. <p>Protection level: internal|role <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only. - @FlaggedApi("android.app.admin.flags.dedicated_device_control_api_enabled") --> <permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE" android:protectionLevel="internal|role" /> diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java index 10aed8d51d09..14292725506e 100644 --- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java +++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java @@ -16,8 +16,6 @@ package android.graphics; -import static com.android.text.flags.Flags.FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -32,7 +30,6 @@ import android.graphics.fonts.FontFamily; import android.graphics.fonts.SystemFonts; import android.graphics.text.PositionedGlyphs; import android.graphics.text.TextRunShaper; -import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.text.FontConfig; @@ -931,7 +928,6 @@ public class TypefaceSystemFallbackTest { return String.format(xml, op, lang, font); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_prepend() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -947,7 +943,6 @@ public class TypefaceSystemFallbackTest { assertB3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_replace() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -963,7 +958,6 @@ public class TypefaceSystemFallbackTest { assertB3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_append() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -979,7 +973,6 @@ public class TypefaceSystemFallbackTest { assertA3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_ScriptMismatch() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); @@ -995,7 +988,6 @@ public class TypefaceSystemFallbackTest { assertA3emFontIsUsed(typeface); } - @RequiresFlagsEnabled(FLAG_VENDOR_CUSTOM_LOCALE_FALLBACK) @Test public void testBuildSystemFallback__Customization_locale_SubscriptMatch() { final ArrayMap<String, Typeface> fontMap = new ArrayMap<>(); diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2f2f76ba10e7..880f30c6cdc0 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -583,6 +583,8 @@ applications that come with the platform <!-- Permissions required for CTS test - AppFunctionManagerTest --> <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" /> <permission name="android.permission.EXECUTE_APP_FUNCTIONS" /> + <!-- Permission required for CTS test - CtsNfcTestCases --> + <permission name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java index ba5628cd2bc1..b7bf0553bcc6 100644 --- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java +++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java @@ -182,10 +182,8 @@ public class FontCustomizationParser { // For ignoring the customization, consume the new-locale-family element but don't // register any customizations. - if (com.android.text.flags.Flags.vendorCustomLocaleFallback()) { - outCustomization.add(new FontConfig.Customization.LocaleFallback( - Locale.forLanguageTag(lang), intOp, family)); - } + outCustomization.add(new FontConfig.Customization.LocaleFallback( + Locale.forLanguageTag(lang), intOp, family)); } else { throw new IllegalArgumentException("Unknown customizationType=" + customizationType); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt new file mode 100644 index 000000000000..05ce36120c4f --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt @@ -0,0 +1,46 @@ +/* + * 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:JvmName("AppToWebUtils") + +package com.android.wm.shell.apptoweb + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri + +private val browserIntent = Intent() + .setAction(Intent.ACTION_VIEW) + .addCategory(Intent.CATEGORY_BROWSABLE) + .setData(Uri.parse("http:")) + +/** + * Returns a boolean indicating whether a given package is a browser app. + */ +fun isBrowserApp(context: Context, packageName: String, userId: Int): Boolean { + browserIntent.setPackage(packageName) + val list = context.packageManager.queryIntentActivitiesAsUser( + browserIntent, PackageManager.MATCH_ALL, userId + ) + + list.forEach { + if (it.activityInfo != null && it.handleAllWebDataURI) { + return true + } + } + return false +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS index bfe1306a60e6..6207e5b020f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OWNERS @@ -1,6 +1,8 @@ atsjenk@google.com jorgegil@google.com madym@google.com +mattsziklay@google.com +mdehaini@google.com pbdr@google.com tkachenkoi@google.com vaniadesmonda@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index aa43c8dab2ad..8a012cd4f6dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -75,6 +75,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser; +import com.android.wm.shell.apptoweb.AppToWebUtils; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.MultiInstanceHelper; @@ -478,6 +479,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @Nullable private Uri getBrowserLink() { + // Do not show browser link in browser applications + final ComponentName baseActivity = mTaskInfo.baseActivity; + if (baseActivity != null && AppToWebUtils.isBrowserApp(mContext, + baseActivity.getPackageName(), mUserContext.getUserId())) { + return null; + } // If the captured link is available and has not expired, return the captured link. // Otherwise, return the generic link which is set to null if a generic link is unavailable. if (mCapturedLink != null && !mCapturedLink.mExpired) { diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt index ec1d4f7854fd..7640cb1fb616 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt @@ -195,6 +195,25 @@ class DesktopModeFlickerScenarios { .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), ) + val CORNER_RESIZE_TO_MAXIMUM_SIZE = + FlickerConfigEntry( + scenarioId = ScenarioId("CORNER_RESIZE_TO_MAXIMUM_SIZE"), + extractor = + TaggedScenarioExtractorBuilder() + .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW) + .setTransitionMatcher( + TaggedCujTransitionMatcher(associatedTransitionRequired = false) + ) + .build(), + assertions = + AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + + listOf( + AppLayerIncreasesInSize(DESKTOP_MODE_APP), + AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP), + AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP) + ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), + ) + val SNAP_RESIZE_LEFT_WITH_BUTTON = FlickerConfigEntry( scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_BUTTON"), diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt new file mode 100644 index 000000000000..0b98ba2a9cd4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizeLandscape.kt @@ -0,0 +1,52 @@ +/* + * 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.wm.shell.flicker + +import android.tools.Rotation +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MAXIMUM_SIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Resize app window using corner resize to the greatest possible height and width in + * landscape mode. + * + * Assert that the maximum window size constraint is maintained. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class ResizeAppToMaximumWindowSizeLandscape : ResizeAppWithCornerResize( + rotation = Rotation.ROTATION_90 +) { + @ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"]) + @Test + override fun resizeAppWithCornerResizeToMaximumSize() = + super.resizeAppWithCornerResizeToMaximumSize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MAXIMUM_SIZE) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.kt new file mode 100644 index 000000000000..b1c04d38a46c --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/ResizeAppToMaximumWindowSizePortrait.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.wm.shell.flicker + +import android.tools.flicker.FlickerConfig +import android.tools.flicker.annotation.ExpectedScenarios +import android.tools.flicker.annotation.FlickerConfigProvider +import android.tools.flicker.config.FlickerConfig +import android.tools.flicker.config.FlickerServiceConfig +import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner +import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MAXIMUM_SIZE +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Resize app window using corner resize to the greatest possible height and width in + * portrait mode. + * + * Assert that the maximum window size constraint is maintained. + */ +@RunWith(FlickerServiceJUnit4ClassRunner::class) +class ResizeAppToMaximumWindowSizePortrait : ResizeAppWithCornerResize() { + @ExpectedScenarios(["CORNER_RESIZE_TO_MAXIMUM_SIZE"]) + @Test + override fun resizeAppWithCornerResizeToMaximumSize() = + super.resizeAppWithCornerResizeToMaximumSize() + + companion object { + @JvmStatic + @FlickerConfigProvider + fun flickerConfigProvider(): FlickerConfig = + FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MAXIMUM_SIZE) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt new file mode 100644 index 000000000000..a4dc52beb85d --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/CloseAllAppsWithAppHeaderExitTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.CloseAllAppsWithAppHeaderExit +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [CloseAllAppsWithAppHeaderExit]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class CloseAllAppsWithAppHeaderExitTest() : CloseAllAppsWithAppHeaderExit() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt new file mode 100644 index 000000000000..3d95f97c09ef --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/EnterDesktopWithDragTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.EnterDesktopWithDrag +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [EnterDesktopWithDrag]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class EnterDesktopWithDragTest : EnterDesktopWithDrag() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt new file mode 100644 index 000000000000..140c5ec15812 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ExitDesktopWithDragToTopDragZoneTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ExitDesktopWithDragToTopDragZone +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ExitDesktopWithDragToTopDragZone]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ExitDesktopWithDragToTopDragZoneTest : ExitDesktopWithDragToTopDragZone() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt new file mode 100644 index 000000000000..3d3dcd09cc63 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MaximizeAppWindowTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.MaximizeAppWindow +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [MaximizeAppWindow]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class MaximizeAppWindowTest : MaximizeAppWindow() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt new file mode 100644 index 000000000000..263e89f69e5a --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppsInDesktopModeTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.OpenAppsInDesktopMode +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [OpenAppsInDesktopMode]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class OpenAppsInDesktopModeTest : OpenAppsInDesktopMode() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt new file mode 100644 index 000000000000..13f4775c1074 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowAndPipTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ResizeAppCornerMultiWindowAndPip +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppCornerMultiWindowAndPip]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppCornerMultiWindowAndPipTest : ResizeAppCornerMultiWindowAndPip() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt new file mode 100644 index 000000000000..bc9bb41bf320 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppCornerMultiWindowTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ResizeAppCornerMultiWindow +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppCornerMultiWindow]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppCornerMultiWindowTest : ResizeAppCornerMultiWindow() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt new file mode 100644 index 000000000000..46168eb7c002 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithCornerResizeTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.ResizeAppWithCornerResize +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppWithCornerResize]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppWithCornerResizeTest : ResizeAppWithCornerResize() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.kt new file mode 100644 index 000000000000..ee2420021339 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/ResizeAppWithEdgeResizeTest.kt @@ -0,0 +1,29 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.server.wm.flicker.helpers.MotionEventHelper +import com.android.wm.shell.scenarios.ResizeAppWithEdgeResize +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [ResizeAppWithEdgeResize]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class ResizeAppWithEdgeResizeTest : + ResizeAppWithEdgeResize(MotionEventHelper.InputMethod.TOUCHPAD) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt new file mode 100644 index 000000000000..38e85c755481 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithButtonTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.SnapResizeAppWindowWithButton +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [SnapResizeAppWindowWithButton]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class SnapResizeAppWindowWithButtonTest : SnapResizeAppWindowWithButton() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt new file mode 100644 index 000000000000..082a3fb0e171 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SnapResizeAppWindowWithDragTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.SnapResizeAppWindowWithDrag +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [SnapResizeAppWindowWithDrag]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class SnapResizeAppWindowWithDragTest : SnapResizeAppWindowWithDrag() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt new file mode 100644 index 000000000000..fdd0d8144130 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/SwitchToOverviewFromDesktopTest.kt @@ -0,0 +1,27 @@ +/* + * 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.wm.shell.functional + +import android.platform.test.annotations.Postsubmit +import com.android.wm.shell.scenarios.SwitchToOverviewFromDesktop +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +/* Functional test for [SwitchToOverviewFromDesktop]. */ +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +class SwitchToOverviewFromDesktopTest : SwitchToOverviewFromDesktop() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt index e9056f3c44d4..351a70094654 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/CloseAllAppsWithAppHeaderExit.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -33,15 +32,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class CloseAllAppsWithAppHeaderExit -@JvmOverloads +@Ignore("Base Test Class") +abstract class CloseAllAppsWithAppHeaderExit constructor(val rotation: Rotation = Rotation.ROTATION_0) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt index ca1dc1a7f658..3f9927f1fab6 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindow.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import com.android.server.wm.flicker.helpers.DesktopModeAppHelper import com.android.server.wm.flicker.helpers.ImeAppHelper import com.android.server.wm.flicker.helpers.MailAppHelper @@ -26,13 +25,11 @@ import com.android.window.flags.Flags import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase() +@Ignore("Test Base Class") +abstract class DragAppWindowMultiWindow : DragAppWindowScenarioTestBase() { private val imeAppHelper = ImeAppHelper(instrumentation) private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt index f4d641411114..967bd29958c2 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterDesktopWithDrag.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import android.tools.flicker.rules.ChangeDisplayOrientationRule @@ -25,19 +24,16 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class EnterDesktopWithDrag -@JvmOverloads +@Ignore("Test Base Class") +abstract class EnterDesktopWithDrag constructor( val rotation: Rotation = Rotation.ROTATION_0, isResizeable: Boolean = true, - isLandscapeApp: Boolean = true + isLandscapeApp: Boolean = true, ) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) { @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt index b616e5347ac9..824c4482c1e6 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ExitDesktopWithDragToTopDragZone.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import com.android.window.flags.Flags @@ -24,19 +23,16 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ExitDesktopWithDragToTopDragZone -@JvmOverloads +@Ignore("Test Base Class") +abstract class ExitDesktopWithDragToTopDragZone constructor( val rotation: Rotation = Rotation.ROTATION_0, isResizeable: Boolean = true, - isLandscapeApp: Boolean = true + isLandscapeApp: Boolean = true, ) : DesktopScenarioCustomAppTestBase(isResizeable, isLandscapeApp) { @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt index a5c794b06c4a..aad266fb8374 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppsInDesktopMode.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.scenarios import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.tools.flicker.rules.ChangeDisplayOrientationRule import android.tools.NavBar import android.tools.Rotation @@ -36,14 +35,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) { +@Ignore("Test Base Class") +abstract class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() @@ -83,4 +80,4 @@ open class OpenAppsInDesktopMode(val rotation: Rotation = Rotation.ROTATION_0) { secondApp.exit(wmHelper) firstApp.exit(wmHelper) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt index b6bca7a94287..bfee3181cbc0 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -34,15 +33,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppCornerMultiWindow -@JvmOverloads +@Ignore("Test Base Class") +abstract class ResizeAppCornerMultiWindow constructor(val rotation: Rotation = Rotation.ROTATION_0, val horizontalChange: Int = 50, val verticalChange: Int = -50) { diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt index 285ea13bb9d4..5b1b64e7c562 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -35,15 +34,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppCornerMultiWindowAndPip -@JvmOverloads +@Ignore("Test Base Class") +abstract class ResizeAppCornerMultiWindowAndPip constructor(val rotation: Rotation = Rotation.ROTATION_0, val horizontalChange: Int = 50, val verticalChange: Int = -50) { diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt index 42940a99b59f..bd25639466a3 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithCornerResize.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -32,16 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppWithCornerResize -@JvmOverloads -constructor( +@Ignore("Test Base Class") +abstract class ResizeAppWithCornerResize( val rotation: Rotation = Rotation.ROTATION_0, val horizontalChange: Int = 200, val verticalChange: Int = -200, @@ -83,6 +78,25 @@ constructor( ) } + @Test + open fun resizeAppWithCornerResizeToMaximumSize() { + val maxResizeChange = 3000 + testApp.cornerResize( + wmHelper, + device, + DesktopModeAppHelper.Corners.RIGHT_TOP, + maxResizeChange, + -maxResizeChange + ) + testApp.cornerResize( + wmHelper, + device, + DesktopModeAppHelper.Corners.LEFT_BOTTOM, + -maxResizeChange, + maxResizeChange + ) + } + @After fun teardown() { testApp.exit(wmHelper) diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt index d094967e91e0..67802387b267 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppWithEdgeResize.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -32,15 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class ResizeAppWithEdgeResize -@JvmOverloads +@Ignore("Test Base Class") +abstract class ResizeAppWithEdgeResize constructor( val inputMethod: MotionEventHelper.InputMethod, val rotation: Rotation = Rotation.ROTATION_90 diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt index 33242db66f9f..2b40497844ef 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithButton.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.scenarios import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import android.tools.traces.parsers.WindowManagerStateHelper @@ -32,15 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class SnapResizeAppWindowWithButton -@JvmOverloads +@Ignore("Test Base Class") +abstract class SnapResizeAppWindowWithButton constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt index 14eb779165bb..b4bd7e1c5211 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithDrag.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.scenarios import android.app.Instrumentation -import android.platform.test.annotations.Postsubmit import android.tools.NavBar import android.tools.Rotation import android.tools.traces.parsers.WindowManagerStateHelper @@ -32,15 +31,12 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class SnapResizeAppWindowWithDrag -@JvmOverloads +@Ignore("Test Base Class") +abstract class SnapResizeAppWindowWithDrag constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() @@ -72,4 +68,4 @@ constructor(private val toLeft: Boolean = true, isResizable: Boolean = true) { fun teardown() { testApp.exit(wmHelper) } -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt index 53e36e23fd95..dad2eb633c72 100644 --- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SwitchToOverviewFromDesktop.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.scenarios -import android.platform.test.annotations.Postsubmit import android.app.Instrumentation import android.tools.NavBar import android.tools.Rotation @@ -31,20 +30,17 @@ import com.android.wm.shell.Utils import org.junit.After import org.junit.Assume import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.BlockJUnit4ClassRunner /** * Base test for opening recent apps overview from desktop mode. * * Navigation mode can be passed as a constructor parameter, by default it is set to gesture navigation. */ -@RunWith(BlockJUnit4ClassRunner::class) -@Postsubmit -open class SwitchToOverviewFromDesktop -@JvmOverloads +@Ignore("Base Test Class") +abstract class SwitchToOverviewFromDesktop constructor(val navigationMode: NavBar = NavBar.MODE_GESTURAL) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp index 09232b64616d..a58493aa47ca 100644 --- a/libs/hostgraphics/Android.bp +++ b/libs/hostgraphics/Android.bp @@ -7,6 +7,17 @@ package { default_applicable_licenses: ["frameworks_base_license"], } +cc_library_headers { + name: "libhostgraphics_headers", + host_supported: true, + export_include_dirs: ["include"], + target: { + windows: { + enabled: true, + }, + }, +} + cc_library_host_static { name: "libhostgraphics", @@ -30,12 +41,13 @@ cc_library_host_static { ], header_libs: [ + "libhostgraphics_headers", "libnativebase_headers", "libnativedisplay_headers", "libnativewindow_headers", ], - export_include_dirs: ["."], + export_include_dirs: ["include"], target: { windows: { diff --git a/libs/hostgraphics/gui/BufferItem.h b/libs/hostgraphics/include/gui/BufferItem.h index e95a9231dfaf..e95a9231dfaf 100644 --- a/libs/hostgraphics/gui/BufferItem.h +++ b/libs/hostgraphics/include/gui/BufferItem.h diff --git a/libs/hostgraphics/gui/BufferItemConsumer.h b/libs/hostgraphics/include/gui/BufferItemConsumer.h index c25941151800..c25941151800 100644 --- a/libs/hostgraphics/gui/BufferItemConsumer.h +++ b/libs/hostgraphics/include/gui/BufferItemConsumer.h diff --git a/libs/hostgraphics/gui/BufferQueue.h b/libs/hostgraphics/include/gui/BufferQueue.h index 67a8c00fd267..67a8c00fd267 100644 --- a/libs/hostgraphics/gui/BufferQueue.h +++ b/libs/hostgraphics/include/gui/BufferQueue.h diff --git a/libs/hostgraphics/gui/ConsumerBase.h b/libs/hostgraphics/include/gui/ConsumerBase.h index 7f7309e8a3a8..7f7309e8a3a8 100644 --- a/libs/hostgraphics/gui/ConsumerBase.h +++ b/libs/hostgraphics/include/gui/ConsumerBase.h diff --git a/libs/hostgraphics/gui/IGraphicBufferConsumer.h b/libs/hostgraphics/include/gui/IGraphicBufferConsumer.h index 14ac4fe71cc8..14ac4fe71cc8 100644 --- a/libs/hostgraphics/gui/IGraphicBufferConsumer.h +++ b/libs/hostgraphics/include/gui/IGraphicBufferConsumer.h diff --git a/libs/hostgraphics/gui/IGraphicBufferProducer.h b/libs/hostgraphics/include/gui/IGraphicBufferProducer.h index 8fd8590d10d7..8fd8590d10d7 100644 --- a/libs/hostgraphics/gui/IGraphicBufferProducer.h +++ b/libs/hostgraphics/include/gui/IGraphicBufferProducer.h diff --git a/libs/hostgraphics/gui/Surface.h b/libs/hostgraphics/include/gui/Surface.h index 2774f89cb54c..2774f89cb54c 100644 --- a/libs/hostgraphics/gui/Surface.h +++ b/libs/hostgraphics/include/gui/Surface.h diff --git a/libs/hostgraphics/ui/Fence.h b/libs/hostgraphics/include/ui/Fence.h index 187c3116f61c..187c3116f61c 100644 --- a/libs/hostgraphics/ui/Fence.h +++ b/libs/hostgraphics/include/ui/Fence.h diff --git a/libs/hostgraphics/ui/GraphicBuffer.h b/libs/hostgraphics/include/ui/GraphicBuffer.h index cda45e4660ca..cda45e4660ca 100644 --- a/libs/hostgraphics/ui/GraphicBuffer.h +++ b/libs/hostgraphics/include/ui/GraphicBuffer.h diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h index c1c30f5379ab..c0cedf12c0ae 100644 --- a/libs/hwui/FeatureFlags.h +++ b/libs/hwui/FeatureFlags.h @@ -25,14 +25,6 @@ namespace android { namespace text_feature { -inline bool fix_double_underline() { -#ifdef __ANDROID__ - return com_android_text_flags_fix_double_underline(); -#else - return true; -#endif // __ANDROID__ -} - inline bool deprecate_ui_fonts() { #ifdef __ANDROID__ return com_android_text_flags_deprecate_ui_fonts(); diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp index a2748b050a2d..236c3736816e 100644 --- a/libs/hwui/HardwareBitmapUploader.cpp +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -318,6 +318,11 @@ bool HardwareBitmapUploader::has1010102Support() { return has101012Support; } +bool HardwareBitmapUploader::has10101010Support() { + static bool has1010110Support = checkSupport(AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM); + return has1010110Support; +} + bool HardwareBitmapUploader::hasAlpha8Support() { static bool hasAlpha8Support = checkSupport(AHARDWAREBUFFER_FORMAT_R8_UNORM); return hasAlpha8Support; @@ -376,6 +381,19 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) { } formatInfo.format = GL_RGBA; break; + case kRGBA_10x6_SkColorType: + formatInfo.isSupported = HardwareBitmapUploader::has10101010Support(); + if (formatInfo.isSupported) { + formatInfo.type = 0; // Not supported in GL + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM; + formatInfo.vkFormat = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16; + } else { + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM; + formatInfo.vkFormat = VK_FORMAT_R8G8B8A8_UNORM; + } + formatInfo.format = 0; // Not supported in GL + break; case kAlpha_8_SkColorType: formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support(); if (formatInfo.isSupported) { diff --git a/libs/hwui/HardwareBitmapUploader.h b/libs/hwui/HardwareBitmapUploader.h index 00ee99648889..76cb80b722d0 100644 --- a/libs/hwui/HardwareBitmapUploader.h +++ b/libs/hwui/HardwareBitmapUploader.h @@ -33,12 +33,14 @@ public: #ifdef __ANDROID__ static bool hasFP16Support(); static bool has1010102Support(); + static bool has10101010Support(); static bool hasAlpha8Support(); #else static bool hasFP16Support() { return true; } static bool has1010102Support() { return true; } + static bool has10101010Support() { return true; } static bool hasAlpha8Support() { return true; } #endif }; diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 72e83afbd96f..9e825fb350d6 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -841,9 +841,6 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& pai sk_sp<SkTextBlob> textBlob(builder.make()); applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); }); - if (!text_feature::fix_double_underline()) { - drawTextDecorations(x, y, totalAdvance, paintCopy); - } } void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 80b6c0385fca..5af4af27babd 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -110,28 +110,26 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance()); MinikinUtils::forFontRun(layout, &paint, f); - if (text_feature::fix_double_underline()) { - Paint copied(paint); - PaintFilter* filter = getPaintFilter(); - if (filter != nullptr) { - filter->filterFullPaint(&copied); + Paint copied(paint); + PaintFilter* filter = getPaintFilter(); + if (filter != nullptr) { + filter->filterFullPaint(&copied); + } + const bool isUnderline = copied.isUnderline(); + const bool isStrikeThru = copied.isStrikeThru(); + if (isUnderline || isStrikeThru) { + const SkScalar left = x; + const SkScalar right = x + layout.getAdvance(); + if (isUnderline) { + const SkScalar top = y + f.getUnderlinePosition(); + drawStroke(left, right, top, f.getUnderlineThickness(), copied, this); } - const bool isUnderline = copied.isUnderline(); - const bool isStrikeThru = copied.isStrikeThru(); - if (isUnderline || isStrikeThru) { - const SkScalar left = x; - const SkScalar right = x + layout.getAdvance(); - if (isUnderline) { - const SkScalar top = y + f.getUnderlinePosition(); - drawStroke(left, right, top, f.getUnderlineThickness(), copied, this); - } - if (isStrikeThru) { - float textSize = paint.getSkFont().getSize(); - const float position = textSize * Paint::kStdStrikeThru_Top; - const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; - const SkScalar top = y + position; - drawStroke(left, right, top, thickness, copied, this); - } + if (isStrikeThru) { + float textSize = paint.getSkFont().getSize(); + const float position = textSize * Paint::kStdStrikeThru_Top; + const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness; + const SkScalar top = y + position; + drawStroke(left, right, top, thickness, copied, this); } } } diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index 0efb2c81af01..d7bf20130b71 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -142,32 +142,30 @@ public: canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance); } - if (text_feature::fix_double_underline()) { - // Extract underline position and thickness. - if (paint.isUnderline()) { - SkFontMetrics metrics; - paint.getSkFont().getMetrics(&metrics); - const float textSize = paint.getSkFont().getSize(); - SkScalar position; - if (!metrics.hasUnderlinePosition(&position)) { - position = textSize * Paint::kStdUnderline_Top; - } - SkScalar thickness; - if (!metrics.hasUnderlineThickness(&thickness)) { - thickness = textSize * Paint::kStdUnderline_Thickness; - } - - // If multiple fonts are used, use the most bottom position and most thick stroke - // width as the underline position. This follows the CSS standard: - // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property - // <quote> - // The exact position and thickness of line decorations is UA-defined in this level. - // However, for underlines and overlines the UA must use a single thickness and - // position on each line for the decorations deriving from a single decorating box. - // </quote> - underlinePosition = std::max(underlinePosition, position); - underlineThickness = std::max(underlineThickness, thickness); + // Extract underline position and thickness. + if (paint.isUnderline()) { + SkFontMetrics metrics; + paint.getSkFont().getMetrics(&metrics); + const float textSize = paint.getSkFont().getSize(); + SkScalar position; + if (!metrics.hasUnderlinePosition(&position)) { + position = textSize * Paint::kStdUnderline_Top; } + SkScalar thickness; + if (!metrics.hasUnderlineThickness(&thickness)) { + thickness = textSize * Paint::kStdUnderline_Thickness; + } + + // If multiple fonts are used, use the most bottom position and most thick stroke + // width as the underline position. This follows the CSS standard: + // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property + // <quote> + // The exact position and thickness of line decorations is UA-defined in this level. + // However, for underlines and overlines the UA must use a single thickness and + // position on each line for the decorations deriving from a single decorating box. + // </quote> + underlinePosition = std::max(underlinePosition, position); + underlineThickness = std::max(underlineThickness, thickness); } } diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index bce84ae77c87..e3023937964e 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -318,6 +318,15 @@ void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions, tailPNext = &deviceFaultFeatures->pNext; } + if (grExtensions.hasExtension(VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME, 1)) { + VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT* formatFeatures = + new VkPhysicalDeviceRGBA10X6FormatsFeaturesEXT; + formatFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RGBA10X6_FORMATS_FEATURES_EXT; + formatFeatures->pNext = nullptr; + *tailPNext = formatFeatures; + tailPNext = &formatFeatures->pNext; + } + // query to get the physical device features mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features); // this looks like it would slow things down, diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp index c70a30477ecf..ecb06d8ca4db 100644 --- a/libs/hwui/tests/unit/UnderlineTest.cpp +++ b/libs/hwui/tests/unit/UnderlineTest.cpp @@ -109,9 +109,7 @@ DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) return f; } -TEST_WITH_FLAGS(UnderlineTest, Roboto, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, - fix_double_underline))) { +TEST(UnderlineTest, Roboto) { float textSize = 100; Paint paint; paint.getSkFont().setSize(textSize); @@ -123,9 +121,7 @@ TEST_WITH_FLAGS(UnderlineTest, Roboto, EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness()); } -TEST_WITH_FLAGS(UnderlineTest, NotoCJK, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, - fix_double_underline))) { +TEST(UnderlineTest, NotoCJK) { float textSize = 100; Paint paint; paint.getSkFont().setSize(textSize); @@ -137,9 +133,7 @@ TEST_WITH_FLAGS(UnderlineTest, NotoCJK, EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness()); } -TEST_WITH_FLAGS(UnderlineTest, Mixture, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags, - fix_double_underline))) { +TEST(UnderlineTest, Mixture) { float textSize = 100; Paint paint; paint.getSkFont().setSize(textSize); diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 6a560b365247..9673c5f03642 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -49,6 +49,10 @@ static inline SkImageInfo createImageInfo(int32_t width, int32_t height, int32_t colorType = kRGBA_1010102_SkColorType; alphaType = kPremul_SkAlphaType; break; + case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM: + colorType = kRGBA_10x6_SkColorType; + alphaType = kPremul_SkAlphaType; + break; case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: colorType = kRGBA_F16_SkColorType; alphaType = kPremul_SkAlphaType; @@ -86,6 +90,8 @@ uint32_t ColorTypeToBufferFormat(SkColorType colorType) { return AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM; case kRGBA_1010102_SkColorType: return AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM; + case kRGBA_10x6_SkColorType: + return AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM; case kARGB_4444_SkColorType: // Hardcoding the value from android::PixelFormat static constexpr uint64_t kRGBA4444 = 7; @@ -108,6 +114,8 @@ SkColorType BufferFormatToColorType(uint32_t format) { return kRGB_565_SkColorType; case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: return kRGBA_1010102_SkColorType; + case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM: + return kRGBA_10x6_SkColorType; case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: return kRGBA_F16_SkColorType; case AHARDWAREBUFFER_FORMAT_R8_UNORM: diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 25fae76768d9..4981cb31ce7d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6984,6 +6984,27 @@ public class AudioManager { } /** + * Test method for enabling/disabling the volume controller long press timeout for checking + * whether two consecutive volume adjustments should be treated as a volume long press. + * + * <p>Used only for testing + * + * @param enable true for enabling, otherwise will be disabled (test mode) + * + * @hide + **/ + @TestApi + @SuppressLint("UnflaggedApi") // @TestApi without associated feature. + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setVolumeControllerLongPressTimeoutEnabled(boolean enable) { + try { + getService().setVolumeControllerLongPressTimeoutEnabled(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Only useful for volume controllers. * @hide */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index a96562d55f67..9af6b2842988 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -303,6 +303,9 @@ interface IAudioService { void notifyVolumeControllerVisible(in IVolumeController controller, boolean visible); + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + oneway void setVolumeControllerLongPressTimeoutEnabled(boolean enable); + boolean isStreamAffectedByRingerMode(int streamType); boolean isStreamAffectedByMute(int streamType); diff --git a/packages/SettingsLib/DataStore/Android.bp b/packages/SettingsLib/DataStore/Android.bp index 86c8f0da83cb..6840e10161f3 100644 --- a/packages/SettingsLib/DataStore/Android.bp +++ b/packages/SettingsLib/DataStore/Android.bp @@ -17,6 +17,7 @@ android_library { "androidx.annotation_annotation", "androidx.collection_collection-ktx", "androidx.core_core-ktx", + "error_prone_annotations", "guava", ], kotlincflags: ["-Xjvm-default=all"], diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt new file mode 100644 index 000000000000..3d4133732915 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyValueStore.kt @@ -0,0 +1,88 @@ +/* + * 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.settingslib.datastore + +import android.content.SharedPreferences + +/** Interface of key-value store. */ +interface KeyValueStore : KeyedObservable<String> { + + /** Returns if the storage contains persistent value of given key. */ + fun contains(key: String): Boolean + + /** Gets default value of given key. */ + fun <T : Any> getDefaultValue(key: String, valueType: Class<T>): T? = + when (valueType) { + Boolean::class.javaObjectType -> false + Float::class.javaObjectType -> 0f + Int::class.javaObjectType -> 0 + Long::class.javaObjectType -> 0 + else -> null + } + as T? + + /** Gets value of given key. */ + fun <T : Any> getValue(key: String, valueType: Class<T>): T? + + /** + * Sets value for given key. + * + * @param key key + * @param valueType value type + * @param value value to set, null means remove + */ + fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) +} + +/** [SharedPreferences] based [KeyValueStore]. */ +interface SharedPreferencesKeyValueStore : KeyValueStore { + + /** [SharedPreferences] of the key-value store. */ + val sharedPreferences: SharedPreferences + + override fun contains(key: String): Boolean = sharedPreferences.contains(key) + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + when (valueType) { + Boolean::class.javaObjectType -> sharedPreferences.getBoolean(key, false) + Float::class.javaObjectType -> sharedPreferences.getFloat(key, 0f) + Int::class.javaObjectType -> sharedPreferences.getInt(key, 0) + Long::class.javaObjectType -> sharedPreferences.getLong(key, 0) + String::class.javaObjectType -> sharedPreferences.getString(key, null) + Set::class.javaObjectType -> sharedPreferences.getStringSet(key, null) + else -> null + } + as T? + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + sharedPreferences.edit().remove(key).apply() + return + } + val edit = sharedPreferences.edit() + when (valueType) { + Boolean::class.javaObjectType -> edit.putBoolean(key, value as Boolean) + Float::class.javaObjectType -> edit.putFloat(key, value as Float) + Int::class.javaObjectType -> edit.putInt(key, value as Int) + Long::class.javaObjectType -> edit.putLong(key, value as Long) + String::class.javaObjectType -> edit.putString(key, value as String) + Set::class.javaObjectType -> edit.putStringSet(key, value as Set<String>) + else -> {} + } + edit.apply() + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt index 4ce1d3790e8b..ec903179f496 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt @@ -19,6 +19,7 @@ package com.android.settingslib.datastore import androidx.annotation.AnyThread import androidx.annotation.GuardedBy import androidx.collection.MutableScatterMap +import com.google.errorprone.annotations.CanIgnoreReturnValue import java.util.WeakHashMap import java.util.concurrent.Executor @@ -62,8 +63,9 @@ interface KeyedObservable<K> { * * @param observer observer to be notified * @param executor executor to run the callback + * @return if the observer is newly added */ - fun addObserver(observer: KeyedObserver<K?>, executor: Executor) + @CanIgnoreReturnValue fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean /** * Adds an observer on given key. @@ -73,14 +75,24 @@ interface KeyedObservable<K> { * @param key key to observe * @param observer observer to be notified * @param executor executor to run the callback + * @return if the observer is newly added */ - fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) + @CanIgnoreReturnValue + fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean - /** Removes observer. */ - fun removeObserver(observer: KeyedObserver<K?>) + /** + * Removes observer. + * + * @return if the observer is found and removed + */ + @CanIgnoreReturnValue fun removeObserver(observer: KeyedObserver<K?>): Boolean - /** Removes observer on given key. */ - fun removeObserver(key: K, observer: KeyedObserver<K>) + /** + * Removes observer on given key. + * + * @return if the observer is found and removed + */ + @CanIgnoreReturnValue fun removeObserver(key: K, observer: KeyedObserver<K>): Boolean /** * Notifies all observers that a change occurs. @@ -111,14 +123,17 @@ open class KeyedDataObservable<K> : KeyedObservable<K> { @GuardedBy("itself") private val keyedObservers = MutableScatterMap<K, WeakHashMap<KeyedObserver<K>, Executor>>() - override fun addObserver(observer: KeyedObserver<K?>, executor: Executor) { + @CanIgnoreReturnValue + override fun addObserver(observer: KeyedObserver<K?>, executor: Executor): Boolean { val oldExecutor = synchronized(observers) { observers.put(observer, executor) } if (oldExecutor != null && oldExecutor != executor) { throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") } + return oldExecutor == null } - override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor) { + @CanIgnoreReturnValue + override fun addObserver(key: K, observer: KeyedObserver<K>, executor: Executor): Boolean { val oldExecutor = synchronized(keyedObservers) { keyedObservers.getOrPut(key) { WeakHashMap() }.put(observer, executor) @@ -126,20 +141,23 @@ open class KeyedDataObservable<K> : KeyedObservable<K> { if (oldExecutor != null && oldExecutor != executor) { throw IllegalStateException("Add $observer twice, old=$oldExecutor, new=$executor") } + return oldExecutor == null } - override fun removeObserver(observer: KeyedObserver<K?>) { - synchronized(observers) { observers.remove(observer) } - } + @CanIgnoreReturnValue + override fun removeObserver(observer: KeyedObserver<K?>) = + synchronized(observers) { observers.remove(observer) } != null - override fun removeObserver(key: K, observer: KeyedObserver<K>) { + @CanIgnoreReturnValue + override fun removeObserver(key: K, observer: KeyedObserver<K>) = synchronized(keyedObservers) { - val observers = keyedObservers[key] - if (observers?.remove(observer) != null && observers.isEmpty()) { + val observers = keyedObservers[key] ?: return false + val removed = observers.remove(observer) != null + if (removed && observers.isEmpty()) { keyedObservers.remove(key) } + removed } - } override fun notifyChange(reason: Int) { // make a copy to avoid potential ConcurrentModificationException diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt new file mode 100644 index 000000000000..4aef0fcfdc15 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsGlobalStore.kt @@ -0,0 +1,81 @@ +/* + * 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.settingslib.datastore + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings.Global +import android.provider.Settings.SettingNotFoundException + +/** + * [KeyValueStore] for [Global] settings. + * + * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`. + */ +class SettingsGlobalStore private constructor(contentResolver: ContentResolver) : + SettingsStore(contentResolver) { + + override val tag: String + get() = "SettingsGlobalStore" + + override fun contains(key: String): Boolean = Global.getString(contentResolver, key) != null + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + try { + when (valueType) { + Boolean::class.javaObjectType -> Global.getInt(contentResolver, key) != 0 + Float::class.javaObjectType -> Global.getFloat(contentResolver, key) + Int::class.javaObjectType -> Global.getInt(contentResolver, key) + Long::class.javaObjectType -> Global.getLong(contentResolver, key) + String::class.javaObjectType -> Global.getString(contentResolver, key) + else -> throw UnsupportedOperationException("Get $key $valueType") + } + as T? + } catch (e: SettingNotFoundException) { + null + } + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + Global.putString(contentResolver, key, null) + return + } + when (valueType) { + Boolean::class.javaObjectType -> + Global.putInt(contentResolver, key, if (value == true) 1 else 0) + Float::class.javaObjectType -> Global.putFloat(contentResolver, key, value as Float) + Int::class.javaObjectType -> Global.putInt(contentResolver, key, value as Int) + Long::class.javaObjectType -> Global.putLong(contentResolver, key, value as Long) + String::class.javaObjectType -> Global.putString(contentResolver, key, value as String) + else -> throw UnsupportedOperationException("Set $key $valueType") + } + } + + companion object { + @Volatile private var instance: SettingsGlobalStore? = null + + @JvmStatic + fun get(context: Context): SettingsGlobalStore = + instance + ?: synchronized(this) { + instance + ?: SettingsGlobalStore(context.applicationContext.contentResolver).also { + instance = it + } + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt new file mode 100644 index 000000000000..9f41ecbc7370 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSecureStore.kt @@ -0,0 +1,81 @@ +/* + * 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.settingslib.datastore + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings.Secure +import android.provider.Settings.SettingNotFoundException + +/** + * [KeyValueStore] for [Secure] settings. + * + * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`. + */ +class SettingsSecureStore private constructor(contentResolver: ContentResolver) : + SettingsStore(contentResolver) { + + override val tag: String + get() = "SettingsSecureStore" + + override fun contains(key: String): Boolean = Secure.getString(contentResolver, key) != null + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + try { + when (valueType) { + Boolean::class.javaObjectType -> Secure.getInt(contentResolver, key) != 0 + Float::class.javaObjectType -> Secure.getFloat(contentResolver, key) + Int::class.javaObjectType -> Secure.getInt(contentResolver, key) + Long::class.javaObjectType -> Secure.getLong(contentResolver, key) + String::class.javaObjectType -> Secure.getString(contentResolver, key) + else -> throw UnsupportedOperationException("Get $key $valueType") + } + as T? + } catch (e: SettingNotFoundException) { + null + } + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + Secure.putString(contentResolver, key, null) + return + } + when (valueType) { + Boolean::class.javaObjectType -> + Secure.putInt(contentResolver, key, if (value == true) 1 else 0) + Float::class.javaObjectType -> Secure.putFloat(contentResolver, key, value as Float) + Int::class.javaObjectType -> Secure.putInt(contentResolver, key, value as Int) + Long::class.javaObjectType -> Secure.putLong(contentResolver, key, value as Long) + String::class.javaObjectType -> Secure.putString(contentResolver, key, value as String) + else -> throw UnsupportedOperationException("Set $key $valueType") + } + } + + companion object { + @Volatile private var instance: SettingsSecureStore? = null + + @JvmStatic + fun get(context: Context): SettingsSecureStore = + instance + ?: synchronized(this) { + instance + ?: SettingsSecureStore(context.applicationContext.contentResolver).also { + instance = it + } + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt new file mode 100644 index 000000000000..59816885f554 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsStore.kt @@ -0,0 +1,104 @@ +/* + * 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.settingslib.datastore + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.util.Log +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicInteger + +/** Base class of the Settings provider data stores. */ +open abstract class SettingsStore(protected val contentResolver: ContentResolver) : + KeyedDataObservable<String>(), KeyValueStore { + + /** + * Counter of observers. + * + * The value is accurate only when [addObserver] and [removeObserver] are called correctly. When + * an observer is not removed (and its weak reference is garbage collected), the content + * observer is not unregistered but this is not a big deal. + */ + private val counter = AtomicInteger() + + private val contentObserver = + object : ContentObserver(Handler(Looper.getMainLooper())) { + override fun onChange(selfChange: Boolean) { + super.onChange(selfChange, null) + } + + override fun onChange(selfChange: Boolean, uri: Uri?) { + val key = uri?.lastPathSegment ?: return + notifyChange(key, DataChangeReason.UPDATE) + } + } + + override fun addObserver(observer: KeyedObserver<String?>, executor: Executor) = + if (super.addObserver(observer, executor)) { + onObserverAdded() + true + } else { + false + } + + override fun addObserver(key: String, observer: KeyedObserver<String>, executor: Executor) = + if (super.addObserver(key, observer, executor)) { + onObserverAdded() + true + } else { + false + } + + private fun onObserverAdded() { + if (counter.getAndIncrement() != 0) return + Log.i(tag, "registerContentObserver") + contentResolver.registerContentObserver( + Settings.Global.getUriFor(""), + true, + contentObserver, + ) + } + + override fun removeObserver(observer: KeyedObserver<String?>) = + if (super.removeObserver(observer)) { + onObserverRemoved() + true + } else { + false + } + + override fun removeObserver(key: String, observer: KeyedObserver<String>) = + if (super.removeObserver(key, observer)) { + onObserverRemoved() + true + } else { + false + } + + private fun onObserverRemoved() { + if (counter.decrementAndGet() != 0) return + Log.i(tag, "unregisterContentObserver") + contentResolver.unregisterContentObserver(contentObserver) + } + + /** Tag for logging. */ + abstract val tag: String +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt new file mode 100644 index 000000000000..6cca7ed59534 --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SettingsSystemStore.kt @@ -0,0 +1,81 @@ +/* + * 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.settingslib.datastore + +import android.content.ContentResolver +import android.content.Context +import android.provider.Settings.SettingNotFoundException +import android.provider.Settings.System + +/** + * [KeyValueStore] for [System] settings. + * + * By default, a boolean type `true` value is stored as `1` and `false` value is stored as `0`. + */ +class SettingsSystemStore private constructor(contentResolver: ContentResolver) : + SettingsStore(contentResolver) { + + override val tag: String + get() = "SettingsSystemStore" + + override fun contains(key: String): Boolean = System.getString(contentResolver, key) != null + + override fun <T : Any> getValue(key: String, valueType: Class<T>): T? = + try { + when (valueType) { + Boolean::class.javaObjectType -> System.getInt(contentResolver, key) != 0 + Float::class.javaObjectType -> System.getFloat(contentResolver, key) + Int::class.javaObjectType -> System.getInt(contentResolver, key) + Long::class.javaObjectType -> System.getLong(contentResolver, key) + String::class.javaObjectType -> System.getString(contentResolver, key) + else -> throw UnsupportedOperationException("Get $key $valueType") + } + as T? + } catch (e: SettingNotFoundException) { + null + } + + override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { + if (value == null) { + System.putString(contentResolver, key, null) + return + } + when (valueType) { + Boolean::class.javaObjectType -> + System.putInt(contentResolver, key, if (value == true) 1 else 0) + Float::class.javaObjectType -> System.putFloat(contentResolver, key, value as Float) + Int::class.javaObjectType -> System.putInt(contentResolver, key, value as Int) + Long::class.javaObjectType -> System.putLong(contentResolver, key, value as Long) + String::class.javaObjectType -> System.putString(contentResolver, key, value as String) + else -> throw UnsupportedOperationException("Set $key $valueType") + } + } + + companion object { + @Volatile private var instance: SettingsSystemStore? = null + + @JvmStatic + fun get(context: Context): SettingsSystemStore = + instance + ?: synchronized(this) { + instance + ?: SettingsSystemStore(context.applicationContext.contentResolver).also { + instance = it + } + } + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt index 0ca91cd4357a..ea17a56715ae 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt @@ -40,8 +40,9 @@ private fun defaultVerbose() = Build.TYPE == "eng" * Note that existing entries in the SharedPreferences will NOT be deleted before restore. * * @param context Context to get SharedPreferences - * @param name Name of the SharedPreferences - * @param mode Operating mode, see [Context.getSharedPreferences] + * @param name Name of the backup restore storage + * @param sharedPreferences SharedPreferences object + * @param filePath shared preferences file path relative to data dir * @param verbose Verbose logging on key/value pairs during backup/restore. Enable for dev only! * @param filter Filter of key/value pairs for backup and restore. */ @@ -50,12 +51,14 @@ open class SharedPreferencesStorage constructor( context: Context, override val name: String, - @get:VisibleForTesting internal val sharedPreferences: SharedPreferences, + override val sharedPreferences: SharedPreferences, + filePath: String = getSharedPreferencesFilePath(context, name), private val codec: BackupCodec? = null, private val verbose: Boolean = defaultVerbose(), private val filter: (String, Any?) -> Boolean = { _, _ -> true }, ) : - BackupRestoreFileStorage(context, context.getSharedPreferencesFilePath(name)), + BackupRestoreFileStorage(context, filePath), + SharedPreferencesKeyValueStore, KeyedObservable<String> by KeyedDataObservable() { @JvmOverloads @@ -66,7 +69,15 @@ constructor( codec: BackupCodec? = null, verbose: Boolean = defaultVerbose(), filter: (String, Any?) -> Boolean = { _, _ -> true }, - ) : this(context, name, context.getSharedPreferences(name, mode), codec, verbose, filter) + ) : this( + context, + name, + context.getSharedPreferences(name, mode), + getSharedPreferencesFilePath(context, name), + codec, + verbose, + filter, + ) /** Name of the intermediate SharedPreferences. */ @VisibleForTesting @@ -80,7 +91,15 @@ constructor( return context.getSharedPreferences(intermediateName, Context.MODE_MULTI_PROCESS) } - private val sharedPreferencesListener = createSharedPreferenceListener() + private val sharedPreferencesListener = + SharedPreferences.OnSharedPreferenceChangeListener { _, key -> + if (key != null) { + notifyChange(key, DataChangeReason.UPDATE) + } else { + // On Android >= R, SharedPreferences.Editor.clear() will trigger this case + notifyChange(DataChangeReason.DELETE) + } + } init { // listener is weakly referenced, so unregister is optional @@ -183,7 +202,8 @@ constructor( else -> { Log.e( LOG_TAG, - "[$name] $operation $key=$value, unknown type: ${value?.javaClass}") + "[$name] $operation $key=$value, unknown type: ${value?.javaClass}", + ) } } } @@ -191,14 +211,31 @@ constructor( } companion object { - private fun Context.getSharedPreferencesFilePath(name: String): String { - val file = getSharedPreferencesFile(name) - return file.relativeTo(dataDirCompat).toString() + /** Returns the storage object of default [SharedPreferences]. */ + @JvmStatic + fun getDefault(context: Context, name: String): SharedPreferencesStorage { + val prefName = getDefaultSharedPreferencesName(context) + return SharedPreferencesStorage( + context, + name, + context.getSharedPreferences(prefName, Context.MODE_PRIVATE), + getSharedPreferencesFilePath(context, prefName), + ) } - /** Returns the absolute path of shared preferences file. */ + /** Returns the name of default [SharedPreferences]. */ @JvmStatic - fun Context.getSharedPreferencesFile(name: String): File { + fun getDefaultSharedPreferencesName(context: Context) = context.packageName + "_preferences" + + /** Returns the shared preferences file path relative to data dir. */ + @JvmStatic + fun getSharedPreferencesFilePath(context: Context, name: String): String { + val file = context.getSharedPreferencesFile(name) + return file.relativeTo(context.dataDirCompat).toString() + } + + /** Returns the absolute path of shared preferences file. */ + private fun Context.getSharedPreferencesFile(name: String): File { // ContextImpl.getSharedPreferencesPath is private return File(getSharedPreferencesDir(), "$name.xml") } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt index 0fdecb034f83..c99d4b386530 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt @@ -45,14 +45,14 @@ class KeyedObserverTest { @Test fun addObserver_sameExecutor() { - keyedObservable.addObserver(observer1, executor1) - keyedObservable.addObserver(observer1, executor1) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(observer1, executor1)).isFalse() } @Test fun addObserver_keyedObserver_sameExecutor() { - keyedObservable.addObserver(key1, keyedObserver1, executor1) - keyedObservable.addObserver(key1, keyedObserver1, executor1) + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isFalse() } @Test @@ -109,15 +109,15 @@ class KeyedObserverTest { @Test fun addObserver_notifyObservers_removeObserver() { - keyedObservable.addObserver(observer1, executor1) - keyedObservable.addObserver(observer2, executor2) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(observer2, executor2)).isTrue() keyedObservable.notifyChange(DataChangeReason.UPDATE) verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) verify(observer2).onKeyChanged(null, DataChangeReason.UPDATE) reset(observer1, observer2) - keyedObservable.removeObserver(observer2) + assertThat(keyedObservable.removeObserver(observer2)).isTrue() keyedObservable.notifyChange(DataChangeReason.DELETE) verify(observer1).onKeyChanged(null, DataChangeReason.DELETE) @@ -126,15 +126,15 @@ class KeyedObserverTest { @Test fun addObserver_keyedObserver_notifyObservers_removeObserver() { - keyedObservable.addObserver(key1, keyedObserver1, executor1) - keyedObservable.addObserver(key2, keyedObserver2, executor2) + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key2, keyedObserver2, executor2)).isTrue() keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) verify(keyedObserver2, never()).onKeyChanged(key2, DataChangeReason.UPDATE) reset(keyedObserver1, keyedObserver2) - keyedObservable.removeObserver(key1, keyedObserver1) + assertThat(keyedObservable.removeObserver(key1, keyedObserver1)).isTrue() keyedObservable.notifyChange(key1, DataChangeReason.DELETE) verify(keyedObserver1, never()).onKeyChanged(key1, DataChangeReason.DELETE) @@ -143,9 +143,9 @@ class KeyedObserverTest { @Test fun notifyChange_addMoreTypeObservers_checkOnKeyChanged() { - keyedObservable.addObserver(observer1, executor1) - keyedObservable.addObserver(key1, keyedObserver1, executor1) - keyedObservable.addObserver(key2, keyedObserver2, executor1) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() + assertThat(keyedObservable.addObserver(key2, keyedObserver2, executor1)).isTrue() keyedObservable.notifyChange(DataChangeReason.UPDATE) verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) @@ -171,25 +171,25 @@ class KeyedObserverTest { fun notifyChange_addObserverWithinCallback() { // ConcurrentModificationException is raised if it is not implemented correctly val observer: KeyedObserver<Any?> = KeyedObserver { _, _ -> - keyedObservable.addObserver(observer1, executor1) + assertThat(keyedObservable.addObserver(observer1, executor1)).isTrue() } - keyedObservable.addObserver(observer, executor1) + assertThat(keyedObservable.addObserver(observer, executor1)).isTrue() keyedObservable.notifyChange(DataChangeReason.UPDATE) - keyedObservable.removeObserver(observer) + assertThat(keyedObservable.removeObserver(observer)).isTrue() } @Test fun notifyChange_KeyedObserver_addObserverWithinCallback() { // ConcurrentModificationException is raised if it is not implemented correctly val keyObserver: KeyedObserver<Any?> = KeyedObserver { _, _ -> - keyedObservable.addObserver(key1, keyedObserver1, executor1) + assertThat(keyedObservable.addObserver(key1, keyedObserver1, executor1)).isTrue() } - keyedObservable.addObserver(key1, keyObserver, executor1) + assertThat(keyedObservable.addObserver(key1, keyObserver, executor1)).isTrue() keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) - keyedObservable.removeObserver(key1, keyObserver) + assertThat(keyedObservable.removeObserver(key1, keyObserver)).isTrue() } } diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index 4387b6f061c3..a0599bb32dd1 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -35,6 +35,7 @@ import android.view.ViewGroup.LayoutParams; import android.widget.FrameLayout; import android.widget.ImageView; +import androidx.annotation.Nullable; import androidx.annotation.RawRes; import androidx.annotation.StringRes; import androidx.preference.Preference; @@ -243,6 +244,14 @@ public class IllustrationPreference extends Preference { } /** + * Gets the content description set by {@link #setContentDescription}. + */ + @Nullable + public CharSequence getContentDescription() { + return mContentDescription; + } + + /** * Gets the lottie illustration resource id. */ public int getLottieAnimationResId() { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 90cee163f8f3..1f3e24254027 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -25,6 +25,13 @@ object SettingsDimension { val paddingLarge = 16.dp val paddingExtraLarge = 24.dp + val spinnerHorizontalPadding = paddingExtraLarge + val spinnerVerticalPadding = paddingLarge + + val actionIconWidth = 32.dp + val actionIconHeight = 40.dp + val actionIconPadding = 4.dp + val itemIconSize = 24.dp val itemIconContainerSize = 72.dp val itemPaddingStart = paddingExtraLarge diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt index 5f320f7ade3f..9bbc16d56811 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt @@ -17,15 +17,24 @@ package com.android.settingslib.spa.widget.scaffold import androidx.appcompat.R +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.FindInPage import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource import com.android.settingslib.spa.framework.compose.LocalNavController +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsShape +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled /** Action that navigates back to last page. */ @Composable @@ -50,6 +59,11 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) { Icon( imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = contentDescription, + modifier = if (isSpaExpressiveEnabled) Modifier + .size(SettingsDimension.actionIconWidth, SettingsDimension.actionIconHeight) + .clip(SettingsShape.CornerExtraLarge) + .background(MaterialTheme.colorScheme.onSurfaceVariant) + .padding(SettingsDimension.actionIconPadding) else Modifier ) } } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt index 4cf741e517be..7d8ee79b3344 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt @@ -39,6 +39,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.compose.horizontalValues import com.android.settingslib.spa.framework.compose.verticalValues import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled import com.android.settingslib.spa.framework.theme.settingsBackground import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -55,6 +56,10 @@ fun SettingsScaffold( ) { ActivityTitle(title) val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + if (isSpaExpressiveEnabled) { + LaunchedEffect(scrollBehavior.state.heightOffsetLimit) { scrollBehavior.collapse() } + } + Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { SettingsTopAppBar(title, scrollBehavior, actions) }, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt index c48a1479555f..6b2db90c6b1f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Spinner.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled data class SpinnerOption( val id: Int, @@ -70,7 +71,10 @@ fun Spinner(options: List<SpinnerOption>, selectedId: Int?, setId: (id: Int) -> ) .selectableGroup(), ) { - val contentPadding = PaddingValues(horizontal = SettingsDimension.itemPaddingEnd) + val contentPadding = if (isSpaExpressiveEnabled) PaddingValues( + horizontal = SettingsDimension.spinnerHorizontalPadding, + vertical = SettingsDimension.spinnerVerticalPadding + ) else PaddingValues(horizontal = SettingsDimension.itemPaddingEnd) Button( modifier = Modifier.semantics { role = Role.DropdownList }, onClick = { expanded = true }, @@ -129,7 +133,11 @@ private fun SpinnerText( text = option?.text ?: "", modifier = modifier .padding(end = SettingsDimension.itemPaddingEnd) - .padding(vertical = SettingsDimension.itemPaddingAround), + .then( + if (!isSpaExpressiveEnabled) + Modifier.padding(vertical = SettingsDimension.itemPaddingAround) + else Modifier + ), color = color, style = MaterialTheme.typography.labelLarge, ) diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt index 043219a65da4..c686708a3c18 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt @@ -61,6 +61,10 @@ class FakeZenModeRepository : ZenModeRepository { mutableModesFlow.value += zenModes } + fun addMode(mode: ZenMode) { + mutableModesFlow.value += mode + } + fun addMode(id: String, @AutomaticZenRule.Type type: Int = AutomaticZenRule.TYPE_UNKNOWN, active: Boolean = false) { mutableModesFlow.value += newMode(id, type, active) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java index ca53fc2ba47e..3f3e1b280850 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java @@ -291,4 +291,12 @@ public class IllustrationPreferenceTest { assertThat(mPreference.isApplyDynamicColor()).isTrue(); } + + @Test + public void setContentDescription_getContentDescription_isEqual() { + final String contentDesc = "content desc"; + mPreference.setContentDescription(contentDesc); + + assertThat(mPreference.getContentDescription().toString()).isEqualTo(contentDesc); + } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 720ec870b05c..f3c5a186563d 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -945,6 +945,9 @@ <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" /> <uses-permission android:name="android.permission.EXECUTE_APP_FUNCTIONS" /> + <!-- Permission required for CTS test - CtsNfcTestCases --> + <uses-permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt index ecb3d8cb04be..c25a45dc5cf6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/AlternateBouncer.kt @@ -52,6 +52,7 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.res.R import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -97,6 +98,7 @@ fun AlternateBouncer( Box { DeviceEntryIcon( viewModel = alternateBouncerDependencies.udfpsIconViewModel, + logger = alternateBouncerDependencies.logger, modifier = Modifier.width { udfpsLocation.width } .height { udfpsLocation.height } @@ -151,13 +153,14 @@ private fun StatusMessage( @Composable private fun DeviceEntryIcon( viewModel: AlternateBouncerUdfpsIconViewModel, + logger: LongPressHandlingViewLogger, modifier: Modifier = Modifier, ) { AndroidView( modifier = modifier, factory = { context -> val view = - DeviceEntryIconView(context, null).apply { + DeviceEntryIconView(context, null, logger = logger).apply { id = R.id.alternate_bouncer_udfps_icon_view contentDescription = context.resources.getString(R.string.accessibility_fingerprint_label) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 46cd58ce6dd0..a525f36c71ce 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -44,6 +44,9 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LongPressHandlingViewLogger +import com.android.systemui.log.dagger.LongPressTouchLog import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper @@ -64,6 +67,7 @@ constructor( private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, private val vibratorHelper: Lazy<VibratorHelper>, + @LongPressTouchLog private val logBuffer: LogBuffer, ) { @Composable fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { @@ -77,19 +81,24 @@ constructor( factory = { context -> val view = if (DeviceEntryUdfpsRefactor.isEnabled) { - DeviceEntryIconView(context, null).apply { - id = R.id.device_entry_icon_view - DeviceEntryIconViewBinder.bind( - applicationScope, - this, - deviceEntryIconViewModel.get(), - deviceEntryForegroundViewModel.get(), - deviceEntryBackgroundViewModel.get(), - falsingManager.get(), - vibratorHelper.get(), - overrideColor, + DeviceEntryIconView( + context, + null, + logger = LongPressHandlingViewLogger(logBuffer, tag = TAG) ) - } + .apply { + id = R.id.device_entry_icon_view + DeviceEntryIconViewBinder.bind( + applicationScope, + this, + deviceEntryIconViewModel.get(), + deviceEntryForegroundViewModel.get(), + deviceEntryBackgroundViewModel.get(), + falsingManager.get(), + vibratorHelper.get(), + overrideColor, + ) + } } else { // KeyguardBottomAreaRefactor.isEnabled LockIconView(context, null).apply { @@ -178,6 +187,10 @@ constructor( return IntRect(center, radius) } + + companion object { + private const val TAG = "LockSection" + } } private val LockIconElementKey = ElementKey("LockIcon") diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt index 4b3a39b367c9..897a8613263f 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt @@ -23,11 +23,13 @@ import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.unit.IntOffset import com.android.compose.nestedscroll.PriorityNestedScrollConnection +import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight +import kotlin.math.max import kotlin.math.roundToInt +import kotlin.math.tanh import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -36,6 +38,7 @@ fun Modifier.stackVerticalOverscroll( coroutineScope: CoroutineScope, canScrollForward: () -> Boolean ): Modifier { + val screenHeight = LocalRawScreenHeight.current val overscrollOffset = remember { Animatable(0f) } val stackNestedScrollConnection = remember { NotificationStackNestedScrollConnection( @@ -43,7 +46,13 @@ fun Modifier.stackVerticalOverscroll( canScrollForward = canScrollForward, onScroll = { offsetAvailable -> coroutineScope.launch { - overscrollOffset.snapTo(overscrollOffset.value + offsetAvailable * 0.3f) + val maxProgress = screenHeight * 0.2f + val tilt = 3f + var offset = + overscrollOffset.value + + maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt)) + offset = max(offset, -1f * maxProgress) + overscrollOffset.snapTo(offset) } }, onStop = { velocityAvailable -> @@ -79,13 +88,7 @@ fun NotificationStackNestedScrollConnection( offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward() }, canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() }, - canContinueScroll = { source -> - if (source == NestedScrollSource.SideEffect) { - stackOffset() > STACK_OVERSCROLL_FLING_MIN_OFFSET - } else { - true - } - }, + canContinueScroll = { stackOffset() > 0f }, canScrollOnFling = true, onStart = { offsetAvailable -> onStart(offsetAvailable) }, onScroll = { offsetAvailable -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index a2beba849b89..91ecfc18a76e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -667,4 +667,3 @@ private val DEBUG_HUN_COLOR = Color(0f, 0f, 1f, 0.2f) private val DEBUG_BOX_COLOR = Color(0f, 1f, 0f, 0.2f) private const val HUN_SNOOZE_POSITIONAL_THRESHOLD_FRACTION = 0.25f private const val HUN_SNOOZE_VELOCITY_THRESHOLD = -70f -internal const val STACK_OVERSCROLL_FLING_MIN_OFFSET = -100f diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 24fef711d397..f3577fab8686 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -190,14 +190,12 @@ internal class DraggableHandlerImpl( private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes { val fromSource = startedPosition?.let { position -> - layoutImpl.swipeSourceDetector - .source( - layoutImpl.lastSize, - position.round(), - layoutImpl.density, - orientation, - ) - ?.resolve(layoutImpl.layoutDirection) + layoutImpl.swipeSourceDetector.source( + layoutImpl.lastSize, + position.round(), + layoutImpl.density, + orientation, + ) } val upOrLeft = diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt index 97c0cef30388..edd697bd7068 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt @@ -54,23 +54,23 @@ class FixedSizeEdgeDetector(val size: Dp) : SwipeSourceDetector { position: IntOffset, density: Density, orientation: Orientation, - ): Edge? { + ): Edge.Resolved? { val axisSize: Int val axisPosition: Int - val topOrLeft: Edge - val bottomOrRight: Edge + val topOrLeft: Edge.Resolved + val bottomOrRight: Edge.Resolved when (orientation) { Orientation.Horizontal -> { axisSize = layoutSize.width axisPosition = position.x - topOrLeft = Edge.Left - bottomOrRight = Edge.Right + topOrLeft = Edge.Resolved.Left + bottomOrRight = Edge.Resolved.Right } Orientation.Vertical -> { axisSize = layoutSize.height axisPosition = position.y - topOrLeft = Edge.Top - bottomOrRight = Edge.Bottom + topOrLeft = Edge.Resolved.Top + bottomOrRight = Edge.Resolved.Bottom } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 061613f999c6..004bb40bb0ad 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -379,6 +379,10 @@ sealed class UserAction { return this to UserActionResult(toScene = scene) } + infix fun to(overlay: OverlayKey): Pair<UserAction, UserActionResult> { + return this to UserActionResult(toOverlay = overlay) + } + /** Resolve this into a [Resolved] user action given [layoutDirection]. */ internal abstract fun resolve(layoutDirection: LayoutDirection): Resolved @@ -475,7 +479,7 @@ interface SwipeSourceDetector { position: IntOffset, density: Density, orientation: Orientation, - ): SwipeSource? + ): SwipeSource.Resolved? } /** The result of performing a [UserAction]. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt index 3fda9b85a5c2..41b015a2ede8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt +++ b/packages/SystemUI/compose/scene/src/com/android/systemui/communal/ui/compose/CommunalSwipeDetector.kt @@ -33,7 +33,7 @@ import kotlin.math.abs * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable * hub. */ -class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) : +class CommunalSwipeDetector(private var lastDirection: SwipeSource.Resolved? = null) : SwipeSourceDetector, SwipeDetector { companion object { private const val TRAVEL_RATIO_THRESHOLD = .5f @@ -44,15 +44,15 @@ class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) : position: IntOffset, density: Density, orientation: Orientation - ): SwipeSource? { + ): SwipeSource.Resolved? { return lastDirection } override fun detectSwipe(change: PointerInputChange): Boolean { if (change.positionChange().x > 0) { - lastDirection = Edge.Left + lastDirection = Edge.Resolved.Left } else { - lastDirection = Edge.Right + lastDirection = Edge.Resolved.Right } // Determine whether the ratio of the distance traveled horizontally to the distance diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt index cceaf57ca82b..dea9283c985c 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt @@ -34,7 +34,7 @@ class FixedSizeEdgeDetectorTest { @Test fun horizontalEdges() { - fun horizontalEdge(position: Int): Edge? = + fun horizontalEdge(position: Int): Edge.Resolved? = detector.source( layoutSize, position = IntOffset(position, 0), @@ -42,17 +42,17 @@ class FixedSizeEdgeDetectorTest { Orientation.Horizontal, ) - assertThat(horizontalEdge(0)).isEqualTo(Edge.Left) - assertThat(horizontalEdge(30)).isEqualTo(Edge.Left) + assertThat(horizontalEdge(0)).isEqualTo(Edge.Resolved.Left) + assertThat(horizontalEdge(30)).isEqualTo(Edge.Resolved.Left) assertThat(horizontalEdge(31)).isEqualTo(null) assertThat(horizontalEdge(69)).isEqualTo(null) - assertThat(horizontalEdge(70)).isEqualTo(Edge.Right) - assertThat(horizontalEdge(100)).isEqualTo(Edge.Right) + assertThat(horizontalEdge(70)).isEqualTo(Edge.Resolved.Right) + assertThat(horizontalEdge(100)).isEqualTo(Edge.Resolved.Right) } @Test fun verticalEdges() { - fun verticalEdge(position: Int): Edge? = + fun verticalEdge(position: Int): Edge.Resolved? = detector.source( layoutSize, position = IntOffset(0, position), @@ -60,11 +60,11 @@ class FixedSizeEdgeDetectorTest { Orientation.Vertical, ) - assertThat(verticalEdge(0)).isEqualTo(Edge.Top) - assertThat(verticalEdge(30)).isEqualTo(Edge.Top) + assertThat(verticalEdge(0)).isEqualTo(Edge.Resolved.Top) + assertThat(verticalEdge(30)).isEqualTo(Edge.Resolved.Top) assertThat(verticalEdge(31)).isEqualTo(null) assertThat(verticalEdge(69)).isEqualTo(null) - assertThat(verticalEdge(70)).isEqualTo(Edge.Bottom) - assertThat(verticalEdge(100)).isEqualTo(Edge.Bottom) + assertThat(verticalEdge(70)).isEqualTo(Edge.Resolved.Bottom) + assertThat(verticalEdge(100)).isEqualTo(Edge.Resolved.Bottom) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java index 09aa286874b9..ca23228459e4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java @@ -17,11 +17,11 @@ package com.android.systemui.accessibility.hearingaid; import static org.mockito.ArgumentMatchers.anyBoolean; +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.bluetooth.BluetoothDevice; import android.testing.TestableLooper; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -51,6 +51,8 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); + private static final int TEST_LAUNCH_SOURCE_ID = 1; + private final FakeExecutor mMainExecutor = new FakeExecutor(new FakeSystemClock()); private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); @Mock @@ -70,7 +72,7 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { @Before public void setUp() { - when(mDialogFactory.create(anyBoolean())).thenReturn(mDialogDelegate); + when(mDialogFactory.create(anyBoolean(), anyInt())).thenReturn(mDialogDelegate); when(mDialogDelegate.createDialog()).thenReturn(mDialog); mManager = new HearingDevicesDialogManager( @@ -86,21 +88,22 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { public void showDialog_existHearingDevice_showPairNewDeviceFalse() { when(mDevicesChecker.isAnyPairedHearingDevice()).thenReturn(true); - mManager.showDialog(mExpandable); + mManager.showDialog(mExpandable, TEST_LAUNCH_SOURCE_ID); mBackgroundExecutor.runAllReady(); mMainExecutor.runAllReady(); - verify(mDialogFactory).create(eq(/* showPairNewDevice= */ false)); + verify(mDialogFactory).create(eq(/* showPairNewDevice= */ false), + eq(TEST_LAUNCH_SOURCE_ID)); } @Test public void showDialog_noHearingDevice_showPairNewDeviceTrue() { when(mDevicesChecker.isAnyPairedHearingDevice()).thenReturn(false); - mManager.showDialog(mExpandable); + mManager.showDialog(mExpandable, TEST_LAUNCH_SOURCE_ID); mBackgroundExecutor.runAllReady(); mMainExecutor.runAllReady(); - verify(mDialogFactory).create(eq(/* showPairNewDevice= */ true)); + verify(mDialogFactory).create(eq(/* showPairNewDevice= */ true), eq(TEST_LAUNCH_SOURCE_ID)); } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java index b7d99d2754fa..65825b2444af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -97,6 +97,8 @@ import com.android.systemui.util.concurrency.FakeExecution; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; +import com.google.android.msdl.domain.MSDLPlayer; + import dagger.Lazy; import org.junit.Before; @@ -185,6 +187,8 @@ public class AuthControllerTest extends SysuiTestCase { private Resources mResources; @Mock private VibratorHelper mVibratorHelper; + @Mock + private MSDLPlayer mMSDLPlayer; private TestableContext mContextSpy; private Execution mExecution; @@ -1066,7 +1070,7 @@ public class AuthControllerTest extends SysuiTestCase { () -> mLogContextInteractor, () -> mPromptSelectionInteractor, () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor, mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper, - mLazyViewCapture); + mLazyViewCapture, mMSDLPlayer); } @Override diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt index 65236f02b635..e3b5f34c8e5a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt @@ -31,6 +31,8 @@ import com.android.systemui.common.ui.data.repository.fakeConfigurationRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.telephony.data.repository.fakeTelephonyRepository @@ -91,6 +93,8 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true) kosmos.telecomManager = telecomManager + + kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "") } @Test @@ -130,6 +134,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { assertThat(metricsLogger.logs.element().category) .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL) verify(activityTaskManager).stopSystemLockTaskMode() + assertThat(kosmos.sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) verify(telecomManager).showInCallScreen(eq(false)) } @@ -156,6 +161,7 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() { assertThat(metricsLogger.logs.element().category) .isEqualTo(MetricsProto.MetricsEvent.ACTION_EMERGENCY_CALL) verify(activityTaskManager).stopSystemLockTaskMode() + assertThat(kosmos.sceneInteractor.currentScene.value).isEqualTo(Scenes.Lockscreen) // TODO(b/25189994): Test the activity has been started once we switch to the // ActivityStarter interface here. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt index bb400f274fbe..f06cd6aec8e0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandlerTest.kt @@ -67,7 +67,8 @@ class LongPressHandlingViewInteractionHandlerTest : SysuiTestCase() { isAttachedToWindow = { isAttachedToWindow }, onLongPressDetected = onLongPressDetected, onSingleTapDetected = onSingleTapDetected, - longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() } + longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }, + allowedTouchSlop = ViewConfiguration.getTouchSlop(), ) underTest.isLongPressHandlingEnabled = true } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt index 4e5806902a10..5bd3645b4cab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractorTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.settings.UserContextProvider import com.android.systemui.settings.userTracker import com.android.systemui.statusbar.phone.KeyguardDismissUtil @@ -40,12 +41,16 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class IssueRecordingUserActionInteractorTest : SysuiTestCase() { + @Mock private lateinit var recordingController: RecordingController + val user = UserHandle(1) val kosmos = Kosmos().also { it.testCase = this } @@ -56,6 +61,7 @@ class IssueRecordingUserActionInteractorTest : SysuiTestCase() { @Before fun setup() { + MockitoAnnotations.initMocks(this) hasCreatedDialogDelegate = false with(kosmos) { val factory = @@ -84,7 +90,8 @@ class IssueRecordingUserActionInteractorTest : SysuiTestCase() { dialogTransitionAnimator, panelInteractor, userTracker, - factory + factory, + recordingController, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt index 91d8e2a75ef0..de3dc5730421 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt @@ -25,7 +25,9 @@ import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R +import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase +import com.android.systemui.SysuiTestableContext import com.android.systemui.common.shared.model.asIcon import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -62,7 +64,12 @@ class ModesTileDataInteractorTest : SysuiTestCase() { context.orCreateTestableResources.apply { addOverride(MODES_DRAWABLE_ID, MODES_DRAWABLE) addOverride(R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE) - addOverride(R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE) + } + + val customPackageContext = SysuiTestableContext(context) + context.prepareCreatePackageContext(CUSTOM_PACKAGE, customPackageContext) + customPackageContext.orCreateTestableResources.apply { + addOverride(CUSTOM_DRAWABLE_ID, CUSTOM_DRAWABLE) } } @@ -146,35 +153,41 @@ class ModesTileDataInteractorTest : SysuiTestCase() { assertThat(tileData?.icon).isEqualTo(MODES_ICON) assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) - // Add an active mode: icon should be the mode icon. No iconResId, because we don't - // really know that it's a system icon. + // Add an active mode with a default icon: icon should be the mode icon, and the + // iconResId is also populated, because we know it's a system icon. zenModeRepository.addMode( - id = "Bedtime", + id = "Bedtime with default icon", type = AutomaticZenRule.TYPE_BEDTIME, active = true ) runCurrent() assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) - assertThat(tileData?.iconResId).isNull() + assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime) - // Add another, less-prioritized mode: icon should remain the first mode icon + // Add another, less-prioritized mode that has a *custom* icon: for now, icon should + // remain the first mode icon zenModeRepository.addMode( - id = "Driving", - type = AutomaticZenRule.TYPE_DRIVING, - active = true + TestModeBuilder() + .setId("Driving with custom icon") + .setType(AutomaticZenRule.TYPE_DRIVING) + .setPackage(CUSTOM_PACKAGE) + .setIconResId(CUSTOM_DRAWABLE_ID) + .setActive(true) + .build() ) runCurrent() assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) - assertThat(tileData?.iconResId).isNull() + assertThat(tileData?.iconResId).isEqualTo(R.drawable.ic_zen_mode_type_bedtime) // Deactivate more important mode: icon should be the less important, still active mode - zenModeRepository.deactivateMode("Bedtime") + // And because it's a package-provided icon, iconResId is not populated. + zenModeRepository.deactivateMode("Bedtime with default icon") runCurrent() - assertThat(tileData?.icon).isEqualTo(DRIVING_ICON) + assertThat(tileData?.icon).isEqualTo(CUSTOM_ICON) assertThat(tileData?.iconResId).isNull() // Deactivate remaining mode: back to the default modes icon - zenModeRepository.deactivateMode("Driving") + zenModeRepository.deactivateMode("Driving with custom icon") runCurrent() assertThat(tileData?.icon).isEqualTo(MODES_ICON) assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) @@ -241,15 +254,17 @@ class ModesTileDataInteractorTest : SysuiTestCase() { private companion object { val TEST_USER = UserHandle.of(1)!! + const val CUSTOM_PACKAGE = "com.some.mode.owner.package" val MODES_DRAWABLE_ID = R.drawable.ic_zen_priority_modes + const val CUSTOM_DRAWABLE_ID = 12345 val MODES_DRAWABLE = TestStubDrawable("modes_icon") val BEDTIME_DRAWABLE = TestStubDrawable("bedtime") - val DRIVING_DRAWABLE = TestStubDrawable("driving") + val CUSTOM_DRAWABLE = TestStubDrawable("custom") val MODES_ICON = MODES_DRAWABLE.asIcon() val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon() - val DRIVING_ICON = DRIVING_DRAWABLE.asIcon() + val CUSTOM_ICON = CUSTOM_DRAWABLE.asIcon() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt index f7bdcb8086ef..c3d45dbbd09a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.qs.tiles.impl.modes.ui import android.app.Flags import android.graphics.drawable.TestStubDrawable -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -109,26 +108,7 @@ class ModesTileMapperTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_MODES_UI_ICONS) - fun state_withEnabledFlag_noIconResId() { - val icon = TestStubDrawable("res123").asIcon() - val model = - ModesTileModel( - isActivated = false, - activeModes = emptyList(), - icon = icon, - iconResId = 123 // Should not be populated, but is ignored even if present - ) - - val state = underTest.map(config, model) - - assertThat(state.icon()).isEqualTo(icon) - assertThat(state.iconRes).isNull() - } - - @Test - @DisableFlags(Flags.FLAG_MODES_UI_ICONS) - fun state_withDisabledFlag_includesIconResId() { + fun state_modelHasIconResId_includesIconResId() { val icon = TestStubDrawable("res123").asIcon() val model = ModesTileModel( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index ec79cc6ef5da..d1804608d130 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -21,6 +21,8 @@ package com.android.systemui.scene.domain.startable import android.app.StatusBarManager import android.hardware.face.FaceManager import android.os.PowerManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -28,6 +30,8 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.uiEventLoggerFake import com.android.internal.policy.IKeyguardDismissCallback +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository import com.android.systemui.authentication.shared.model.AuthenticationMethodModel @@ -48,6 +52,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository import com.android.systemui.keyguard.data.repository.biometricSettingsRepository @@ -95,6 +100,7 @@ import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvision import com.android.systemui.statusbar.sysuiStatusBarStateController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow @@ -137,6 +143,8 @@ class SceneContainerStartableTest : SysuiTestCase() { private val powerInteractor = kosmos.powerInteractor private val fakeTrustRepository = kosmos.fakeTrustRepository private val uiEventLoggerFake = kosmos.uiEventLoggerFake + private val msdlPlayer = kosmos.fakeMSDLPlayer + private val authInteractionProperties = AuthInteractionProperties() private lateinit var underTest: SceneContainerStartable @@ -654,6 +662,7 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptics_onSuccessfulLockscreenAuth_udfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -680,6 +689,31 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_udfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasUdfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptics_onSuccessfulLockscreenAuth_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -707,6 +741,32 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playSuccessMSDLHaptics_onSuccessfulLockscreenAuth_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + allowHapticsOnSfps() + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.UNLOCK) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playErrorHaptics_onFailedLockscreenAuth_udfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -727,6 +787,27 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playMSDLErrorHaptics_onFailedLockscreenAuth_udfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasUdfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + updateFingerprintAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playErrorHaptics_onFailedLockscreenAuth_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -747,6 +828,27 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playMSDLErrorHaptics_onFailedLockscreenAuth_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + updateFingerprintAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsSuccessHaptics_whenPowerButtonDown_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -774,6 +876,32 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLSuccessHaptics_whenPowerButtonDown_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + allowHapticsOnSfps(isPowerButtonDown = true) + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -801,6 +929,32 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLSuccessHaptics_whenPowerButtonRecentlyPressed_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playSuccessHaptic by + collectLastValue(deviceEntryHapticsInteractor.playSuccessHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + allowHapticsOnSfps(lastPowerPress = 50) + unlockWithFingerprintAuth() + + assertThat(playSuccessHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + + updateFingerprintAuthStatus(isSuccess = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Gone) + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsErrorHaptics_whenPowerButtonDown_sfps() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -822,6 +976,28 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLErrorHaptics_whenPowerButtonDown_sfps() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasSfps = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + kosmos.fakeKeyEventRepository.setPowerButtonDown(true) + updateFingerprintAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun skipsFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) @@ -842,6 +1018,26 @@ class SceneContainerStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() = + testScope.runTest { + val currentSceneKey by collectLastValue(sceneInteractor.currentScene) + val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) + + setupBiometricAuth(hasUdfps = true, hasFace = true) + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isFalse() + + underTest.start() + updateFaceAuthStatus(isSuccess = false) + + assertThat(playErrorHaptic).isNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @Test fun hydrateSystemUiState() = testScope.runTest { val transitionStateFlow = prepareState() diff --git a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml index 59cfeccbeb36..e7a40d129d50 100644 --- a/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml +++ b/packages/SystemUI/res/layout/notification_template_en_route_contracted.xml @@ -16,7 +16,7 @@ <com.android.systemui.statusbar.notification.row.ui.view.EnRouteView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/status_bar_latest_event_content" + android:id="@*android:id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" diff --git a/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml new file mode 100644 index 000000000000..ca6d66a370bd --- /dev/null +++ b/packages/SystemUI/res/layout/notification_template_en_route_expanded.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2014 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.systemui.statusbar.notification.row.ui.view.EnRouteView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@*android:id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:clipChildren="false" + android:tag="big" + > + + <LinearLayout + android:id="@*android:id/notification_action_list_margin_target" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="@*android:dimen/notification_content_margin" + android:orientation="vertical" + > + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="top" + > + + <include layout="@*android:layout/notification_template_header" /> + + <LinearLayout + android:id="@*android:id/notification_main_column" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@*android:dimen/notification_content_margin_start" + android:layout_marginEnd="@*android:dimen/notification_content_margin_end" + android:layout_marginTop="@*android:dimen/notification_content_margin_top" + android:orientation="vertical" + > + + <include layout="@*android:layout/notification_template_part_line1" /> + + <include layout="@*android:layout/notification_template_text_multiline" /> + + <include + android:layout_width="match_parent" + android:layout_height="@*android:dimen/notification_progress_bar_height" + android:layout_marginTop="@*android:dimen/notification_progress_margin_top" + layout="@*android:layout/notification_template_progress" + /> + </LinearLayout> + + <include layout="@*android:layout/notification_template_right_icon" /> + </FrameLayout> + + <ViewStub + android:layout="@*android:layout/notification_material_reply_text" + android:id="@*android:id/notification_material_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + /> + + <include + layout="@*android:layout/notification_template_smart_reply_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@*android:dimen/notification_content_margin_start" + android:layout_marginEnd="@*android:dimen/notification_content_margin_end" + android:layout_marginTop="@*android:dimen/notification_content_margin" + /> + + <include layout="@*android:layout/notification_material_action_list" /> + </LinearLayout> +</com.android.systemui.statusbar.notification.row.ui.view.EnRouteView> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index d08653c3cf1b..60edaae21bc0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -52,7 +52,6 @@ import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HapClientProfile; @@ -105,7 +104,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private final AudioManager mAudioManager; private final LocalBluetoothProfileManager mProfileManager; private final HapClientProfile mHapClientProfile; - private final UiEventLogger mUiEventLogger; + private final HearingDevicesUiEventLogger mUiEventLogger; + private final int mLaunchSourceId; private HearingDevicesListAdapter mDeviceListAdapter; private HearingDevicesPresetsController mPresetsController; private Context mApplicationContext; @@ -153,20 +153,22 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, public interface Factory { /** Create a {@link HearingDevicesDialogDelegate} instance */ HearingDevicesDialogDelegate create( - boolean showPairNewDevice); + boolean showPairNewDevice, + @HearingDevicesUiEventLogger.LaunchSourceId int launchSource); } @AssistedInject public HearingDevicesDialogDelegate( @Application Context applicationContext, @Assisted boolean showPairNewDevice, + @Assisted @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId, SystemUIDialog.Factory systemUIDialogFactory, ActivityStarter activityStarter, DialogTransitionAnimator dialogTransitionAnimator, @Nullable LocalBluetoothManager localBluetoothManager, @Main Handler handler, AudioManager audioManager, - UiEventLogger uiEventLogger) { + HearingDevicesUiEventLogger uiEventLogger) { mApplicationContext = applicationContext; mShowPairNewDevice = showPairNewDevice; mSystemUIDialogFactory = systemUIDialogFactory; @@ -178,6 +180,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mProfileManager = localBluetoothManager.getProfileManager(); mHapClientProfile = mProfileManager.getHapClientProfile(); mUiEventLogger = uiEventLogger; + mLaunchSourceId = launchSourceId; } @Override @@ -191,7 +194,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, @Override public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, mLaunchSourceId); dismissDialogIfExists(); Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS); Bundle bundle = new Bundle(); @@ -207,15 +210,17 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, CachedBluetoothDevice cachedBluetoothDevice = deviceItem.getCachedBluetoothDevice(); switch (deviceItem.getType()) { case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT, + mLaunchSourceId); cachedBluetoothDevice.disconnect(); } case AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE, + mLaunchSourceId); cachedBluetoothDevice.setActive(); } case SAVED_BLUETOOTH_DEVICE -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT, mLaunchSourceId); cachedBluetoothDevice.connect(); } } @@ -275,7 +280,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, if (mLocalBluetoothManager == null) { return; } - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW, mLaunchSourceId); mPairButton = dialog.requireViewById(R.id.pair_new_device_button); mDeviceList = dialog.requireViewById(R.id.device_list); mPresetSpinner = dialog.requireViewById(R.id.preset_spinner); @@ -363,7 +368,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT, + mLaunchSourceId); mPresetsController.selectPreset( mPresetsController.getAllPresetInfo().get(position).getIndex()); } @@ -381,7 +387,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) { if (visibility == VISIBLE) { mPairButton.setOnClickListener(v -> { - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, mLaunchSourceId); dismissDialogIfExists(); final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -485,7 +491,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, final String name = intent.getComponent() != null ? intent.getComponent().flattenToString() : intent.getPackage() + "/" + intent.getAction(); - mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK, 0, name); + mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK, + mLaunchSourceId, name); dismissDialogIfExists(); mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, mDialogTransitionAnimator.createActivityTransitionController(view)); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java index bc4cb45582ff..3d24177a8029 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java @@ -70,8 +70,10 @@ public class HearingDevicesDialogManager { * Shows the dialog. * * @param expandable {@link Expandable} from which the dialog is shown. + * @param launchSourceId the id indicates where the dialog is launched from. */ - public void showDialog(Expandable expandable) { + public void showDialog(Expandable expandable, + @HearingDevicesUiEventLogger.LaunchSourceId int launchSourceId) { if (mDialog != null) { if (DEBUG) { Log.d(TAG, "HearingDevicesDialog already showing. Destroy it first."); @@ -91,7 +93,8 @@ public class HearingDevicesDialogManager { }); pairedHearingDeviceCheckTask.addListener(() -> { try { - mDialog = mDialogFactory.create(!pairedHearingDeviceCheckTask.get()).createDialog(); + mDialog = mDialogFactory.create(!pairedHearingDeviceCheckTask.get(), + launchSourceId).createDialog(); if (expandable != null) { DialogTransitionAnimator.Controller controller = diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java index 6a34d1969fe5..02e65fd9031b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogReceiver.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility.hearingaid; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_A11Y; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -46,7 +48,7 @@ public class HearingDevicesDialogReceiver extends BroadcastReceiver { } if (ACTION.equals(intent.getAction())) { - mDialogManager.showDialog(/* view= */ null); + mDialogManager.showDialog(/* expandable= */ null, LAUNCH_SOURCE_A11Y); } } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java deleted file mode 100644 index 3fbe56eccef2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.accessibility.hearingaid; - -import com.android.internal.logging.UiEvent; -import com.android.internal.logging.UiEventLogger; - -public enum HearingDevicesUiEvent implements UiEventLogger.UiEventEnum { - - @UiEvent(doc = "Hearing devices dialog is shown") - HEARING_DEVICES_DIALOG_SHOW(1848), - @UiEvent(doc = "Pair new device") - HEARING_DEVICES_PAIR(1849), - @UiEvent(doc = "Connect to the device") - HEARING_DEVICES_CONNECT(1850), - @UiEvent(doc = "Disconnect from the device") - HEARING_DEVICES_DISCONNECT(1851), - @UiEvent(doc = "Set the device as active device") - HEARING_DEVICES_SET_ACTIVE(1852), - @UiEvent(doc = "Click on the device gear to enter device detail page") - HEARING_DEVICES_GEAR_CLICK(1853), - @UiEvent(doc = "Select a preset from preset spinner") - HEARING_DEVICES_PRESET_SELECT(1854), - @UiEvent(doc = "Click on related tool") - HEARING_DEVICES_RELATED_TOOL_CLICK(1856); - - private final int mId; - - HearingDevicesUiEvent(int id) { - mId = id; - } - - @Override - public int getId() { - return mId; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt new file mode 100644 index 000000000000..9e77b02be495 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.kt @@ -0,0 +1,35 @@ +/* + * 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.accessibility.hearingaid + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class HearingDevicesUiEvent(private val id: Int) : UiEventLogger.UiEventEnum { + + @UiEvent(doc = "Hearing devices dialog is shown") HEARING_DEVICES_DIALOG_SHOW(1848), + @UiEvent(doc = "Pair new device") HEARING_DEVICES_PAIR(1849), + @UiEvent(doc = "Connect to the device") HEARING_DEVICES_CONNECT(1850), + @UiEvent(doc = "Disconnect from the device") HEARING_DEVICES_DISCONNECT(1851), + @UiEvent(doc = "Set the device as active device") HEARING_DEVICES_SET_ACTIVE(1852), + @UiEvent(doc = "Click on the device gear to enter device detail page") + HEARING_DEVICES_GEAR_CLICK(1853), + @UiEvent(doc = "Select a preset from preset spinner") HEARING_DEVICES_PRESET_SELECT(1854), + @UiEvent(doc = "Click on related tool") HEARING_DEVICES_RELATED_TOOL_CLICK(1856); + + override fun getId(): Int = this.id +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.kt new file mode 100644 index 000000000000..0b32cfc9742b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEventLogger.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.accessibility.hearingaid + +import android.annotation.IntDef +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +class HearingDevicesUiEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) { + + /** Logs the given event */ + fun log(event: UiEventLogger.UiEventEnum, launchSourceId: Int) { + log(event, launchSourceId, null) + } + + fun log(event: UiEventLogger.UiEventEnum, launchSourceId: Int, pkgName: String?) { + uiEventLogger.log(event, launchSourceId, pkgName) + } + + /** + * The possible launch source of hearing devices dialog + * + * @hide + */ + @IntDef(LAUNCH_SOURCE_UNKNOWN, LAUNCH_SOURCE_A11Y, LAUNCH_SOURCE_QS_TILE) + annotation class LaunchSourceId + + companion object { + const val LAUNCH_SOURCE_UNKNOWN = 0 + const val LAUNCH_SOURCE_A11Y = 1 // launch from AccessibilityManagerService + const val LAUNCH_SOURCE_QS_TILE = 2 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 970fdeabcaf8..69ab976c5301 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -78,6 +78,8 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.google.android.msdl.domain.MSDLPlayer; + import kotlin.Lazy; import kotlinx.coroutines.CoroutineScope; @@ -157,6 +159,8 @@ public class AuthContainerView extends LinearLayout private final @Background DelayableExecutor mBackgroundExecutor; + private final MSDLPlayer mMSDLPlayer; + // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason; // HAT received from LockSettingsService when credential is verified. @@ -292,7 +296,8 @@ public class AuthContainerView extends LinearLayout @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull @Background DelayableExecutor bgExecutor, @NonNull VibratorHelper vibratorHelper, - Lazy<ViewCapture> lazyViewCapture) { + Lazy<ViewCapture> lazyViewCapture, + @NonNull MSDLPlayer msdlPlayer) { super(config.mContext); mConfig = config; @@ -309,6 +314,7 @@ public class AuthContainerView extends LinearLayout .getDimension(R.dimen.biometric_dialog_animation_translation_offset); mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); + mMSDLPlayer = msdlPlayer; final BiometricModalities biometricModalities = new BiometricModalities( Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), @@ -379,7 +385,7 @@ public class AuthContainerView extends LinearLayout getJankListener(mLayout, TRANSIT, BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); + vibratorHelper, mMSDLPlayer); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 097ab72522d6..b39aae94ed4e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -89,6 +89,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import com.google.android.msdl.domain.MSDLPlayer; + import dagger.Lazy; import kotlin.Unit; @@ -183,6 +185,7 @@ public class AuthController implements private final @Background DelayableExecutor mBackgroundExecutor; private final DisplayInfo mCachedDisplayInfo = new DisplayInfo(); @NonNull private final VibratorHelper mVibratorHelper; + @NonNull private final MSDLPlayer mMSDLPlayer; private final kotlin.Lazy<ViewCapture> mLazyViewCapture; @@ -742,7 +745,8 @@ public class AuthController implements @Background DelayableExecutor bgExecutor, @NonNull UdfpsUtils udfpsUtils, @NonNull VibratorHelper vibratorHelper, - Lazy<ViewCapture> daggerLazyViewCapture) { + Lazy<ViewCapture> daggerLazyViewCapture, + @NonNull MSDLPlayer msdlPlayer) { mContext = context; mExecution = execution; mUserManager = userManager; @@ -764,6 +768,7 @@ public class AuthController implements mUdfpsUtils = udfpsUtils; mApplicationCoroutineScope = applicationCoroutineScope; mVibratorHelper = vibratorHelper; + mMSDLPlayer = msdlPlayer; mLogContextInteractor = logContextInteractor; mPromptSelectorInteractor = promptSelectorInteractorProvider; @@ -1327,7 +1332,7 @@ public class AuthController implements wakefulnessLifecycle, userManager, lockPatternUtils, mInteractionJankMonitor, mPromptSelectorInteractor, viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper, - mLazyViewCapture); + mLazyViewCapture, mMSDLPlayer); } @Override 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 0b440ad81fb5..e7e8d8f80cda 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 @@ -25,7 +25,6 @@ import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.Flags import android.hardware.face.FaceManager import android.util.Log -import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.View import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO @@ -59,6 +58,7 @@ import com.android.systemui.common.ui.view.onTouchListener import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper +import com.google.android.msdl.domain.MSDLPlayer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.combine @@ -83,6 +83,7 @@ object BiometricViewBinder { legacyCallback: Spaghetti.Callback, applicationScope: CoroutineScope, vibratorHelper: VibratorHelper, + msdlPlayer: MSDLPlayer, ): Spaghetti { val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!! @@ -434,21 +435,27 @@ object BiometricViewBinder { // Play haptics launch { viewModel.hapticsToPlay.collect { haptics -> - if (haptics.hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) { - if (haptics.flag != null) { - vibratorHelper.performHapticFeedback( - view, - haptics.hapticFeedbackConstant, - haptics.flag, - ) - } else { - vibratorHelper.performHapticFeedback( - view, - haptics.hapticFeedbackConstant, - ) + when (haptics) { + is PromptViewModel.HapticsToPlay.HapticConstant -> { + if (haptics.flag != null) { + vibratorHelper.performHapticFeedback( + view, + haptics.constant, + haptics.flag, + ) + } else { + vibratorHelper.performHapticFeedback( + view, + haptics.constant, + ) + } + } + is PromptViewModel.HapticsToPlay.MSDL -> { + msdlPlayer.playToken(haptics.token, haptics.properties) } - viewModel.clearHaptics() + is PromptViewModel.HapticsToPlay.None -> {} } + viewModel.clearHaptics() } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 4c2fe07f92bb..168ba11309cc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -35,7 +35,9 @@ import android.util.Log import android.util.RotationUtils import android.view.HapticFeedbackConstants import android.view.MotionEvent +import com.android.keyguard.AuthInteractionProperties import com.android.launcher3.icons.IconProvider +import com.android.systemui.Flags.msdlFeedback import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.Utils.isSystem @@ -53,6 +55,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus import com.android.systemui.res.R import com.android.systemui.util.kotlin.combine +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.InteractionProperties import javax.inject.Inject import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope @@ -245,8 +249,9 @@ constructor( private val _forceLargeSize = MutableStateFlow(false) private val _forceMediumSize = MutableStateFlow(false) - private val _hapticsToPlay = - MutableStateFlow(HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, /* flag= */ null)) + private val authInteractionProperties = AuthInteractionProperties() + private val _hapticsToPlay: MutableStateFlow<HapticsToPlay> = + MutableStateFlow(HapticsToPlay.None) /** Event fired to the view indicating a [HapticsToPlay] */ val hapticsToPlay = _hapticsToPlay.asStateFlow() @@ -939,26 +944,52 @@ constructor( } private fun vibrateOnSuccess() { - _hapticsToPlay.value = - HapticsToPlay( - HapticFeedbackConstants.BIOMETRIC_CONFIRM, - null, - ) + val haptics = + if (msdlFeedback()) { + HapticsToPlay.MSDL(MSDLToken.UNLOCK, authInteractionProperties) + } else { + HapticsToPlay.HapticConstant( + HapticFeedbackConstants.BIOMETRIC_CONFIRM, + flag = null, + ) + } + _hapticsToPlay.value = haptics } private fun vibrateOnError() { - _hapticsToPlay.value = - HapticsToPlay( - HapticFeedbackConstants.BIOMETRIC_REJECT, - null, - ) + val haptics = + if (msdlFeedback()) { + HapticsToPlay.MSDL(MSDLToken.FAILURE, authInteractionProperties) + } else { + HapticsToPlay.HapticConstant( + HapticFeedbackConstants.BIOMETRIC_REJECT, + flag = null, + ) + } + _hapticsToPlay.value = haptics } /** Clears the [hapticsToPlay] variable by setting its constant to the NO_HAPTICS default. */ fun clearHaptics() { - _hapticsToPlay.update { previous -> - HapticsToPlay(HapticFeedbackConstants.NO_HAPTICS, previous.flag) - } + _hapticsToPlay.update { HapticsToPlay.None } + } + + /** The state of haptic feedback to play. */ + sealed interface HapticsToPlay { + /** + * Haptics using [HapticFeedbackConstants]. It is composed by a [HapticFeedbackConstants] + * and a [HapticFeedbackConstants] flag. + */ + data class HapticConstant(val constant: Int, val flag: Int?) : HapticsToPlay + + /** + * Haptics using MSDL feedback. It is composed by a [MSDLToken] and optional + * [InteractionProperties] + */ + data class MSDL(val token: MSDLToken, val properties: InteractionProperties?) : + HapticsToPlay + + data object None : HapticsToPlay } companion object { @@ -1095,9 +1126,3 @@ enum class FingerprintStartMode { val isStarted: Boolean get() = this == Normal || this == Delayed } - -/** - * The state of haptic feedback to play. It is composed by a [HapticFeedbackConstants] and a - * [HapticFeedbackConstants] flag. - */ -data class HapticsToPlay(val hapticFeedbackConstant: Int, val flag: Int?) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt index f36ef6630a48..8b5a09b3d9fd 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt @@ -34,10 +34,14 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.doze.DozeLogger +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.EmergencyDialerConstants +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -69,6 +73,7 @@ constructor( private val emergencyDialerIntentFactory: EmergencyDialerIntentFactory, private val metricsLogger: MetricsLogger, private val dozeLogger: DozeLogger, + private val sceneInteractor: Lazy<SceneInteractor>, ) { /** The bouncer action button. If `null`, the button should not be shown. */ val actionButton: Flow<BouncerActionButtonModel?> = @@ -158,14 +163,17 @@ constructor( } private fun prepareToPerformAction() { - // TODO(b/308001302): Trigger occlusion and resetting bouncer state. + if (SceneContainerFlag.isEnabled) { + sceneInteractor.get().changeScene(Scenes.Lockscreen, "Bouncer action button clicked") + } + metricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL) activityTaskManager.stopSystemLockTaskMode() } @SuppressLint("MissingPermission") private fun returnToCall() { - telecomManager?.showInCallScreen(/* showDialpad = */ false) + telecomManager?.showInCallScreen(/* showDialpad= */ false) } private val <T> Flow<T>.asUnitFlow: Flow<Unit> diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt index b6ace81d18ba..9c4736a13b46 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt @@ -27,6 +27,7 @@ import android.view.ViewConfiguration import android.view.accessibility.AccessibilityNodeInfo import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.shade.TouchLogger import kotlin.math.pow import kotlin.math.sqrt @@ -42,6 +43,8 @@ class LongPressHandlingView( context: Context, attrs: AttributeSet?, longPressDuration: () -> Long, + allowedTouchSlop: Int = ViewConfiguration.getTouchSlop(), + logger: LongPressHandlingViewLogger? = null, ) : View( context, @@ -97,6 +100,8 @@ class LongPressHandlingView( }, onSingleTapDetected = { listener?.onSingleTapDetected(this@LongPressHandlingView) }, longPressDuration = longPressDuration, + allowedTouchSlop = allowedTouchSlop, + logger = logger, ) } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt index d3fc610bc52e..4e38a4913fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt @@ -17,7 +17,7 @@ package com.android.systemui.common.ui.view -import android.view.ViewConfiguration +import com.android.systemui.log.LongPressHandlingViewLogger import kotlinx.coroutines.DisposableHandle /** Encapsulates logic to handle complex touch interactions with a [LongPressHandlingView]. */ @@ -35,6 +35,14 @@ class LongPressHandlingViewInteractionHandler( private val onSingleTapDetected: () -> Unit, /** Time for the touch to be considered a long-press in ms */ var longPressDuration: () -> Long, + /** + * Default touch slop that is allowed, if the movement between [MotionEventModel.Down] and + * [MotionEventModel.Up] is more than [allowedTouchSlop] then the touch is not processed as + * single tap or a long press. + */ + val allowedTouchSlop: Int, + /** Optional logger that can be passed in to log touch events */ + val logger: LongPressHandlingViewLogger? = null, ) { sealed class MotionEventModel { object Other : MotionEventModel() @@ -70,22 +78,26 @@ class LongPressHandlingViewInteractionHandler( true } is MotionEventModel.Move -> { - if (event.distanceMoved > ViewConfiguration.getTouchSlop()) { + if (event.distanceMoved > allowedTouchSlop) { + logger?.cancelingLongPressDueToTouchSlop(event.distanceMoved, allowedTouchSlop) cancelScheduledLongPress() } false } is MotionEventModel.Up -> { + logger?.onUpEvent(event.distanceMoved, allowedTouchSlop, event.gestureDuration) cancelScheduledLongPress() if ( - event.distanceMoved <= ViewConfiguration.getTouchSlop() && + event.distanceMoved <= allowedTouchSlop && event.gestureDuration < longPressDuration() ) { + logger?.dispatchingSingleTap() dispatchSingleTap() } false } is MotionEventModel.Cancel -> { + logger?.motionEventCancelled() cancelScheduledLongPress() false } @@ -97,15 +109,18 @@ class LongPressHandlingViewInteractionHandler( x: Int, y: Int, ) { + val duration = longPressDuration() + logger?.schedulingLongPress(duration) scheduledLongPressHandle = postDelayed( { + logger?.longPressTriggered() dispatchLongPress( x = x, y = y, ) }, - longPressDuration(), + duration, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 91a7f7fc66bd..76962732ad01 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -42,6 +42,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerWindowViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scrim.ScrimView @@ -191,6 +192,7 @@ constructor( optionallyAddUdfpsViews( view = view, + logger = alternateBouncerDependencies.logger, udfpsIconViewModel = alternateBouncerDependencies.udfpsIconViewModel, udfpsA11yOverlayViewModel = alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel, @@ -248,6 +250,7 @@ constructor( private fun optionallyAddUdfpsViews( view: ConstraintLayout, + logger: LongPressHandlingViewLogger, udfpsIconViewModel: AlternateBouncerUdfpsIconViewModel, udfpsA11yOverlayViewModel: Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>, ) { @@ -276,7 +279,7 @@ constructor( var udfpsView = view.getViewById(udfpsViewId) if (udfpsView == null) { udfpsView = - DeviceEntryIconView(view.context, null).apply { + DeviceEntryIconView(view.context, null, logger = logger).apply { id = udfpsViewId contentDescription = context.resources.getString( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index 4d6577c0423a..b951b736abf2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.content.res.ColorStateList +import android.util.Log import android.util.StateSet import android.view.HapticFeedbackConstants import android.view.View @@ -83,6 +84,11 @@ object DeviceEntryIconViewBinder { if ( !isA11yAction && falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY) ) { + Log.d( + TAG, + "Long press rejected because it is not a11yAction " + + "and it is a falseLongTap" + ) return } vibratorHelper.performHapticFeedback( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index 3e6d5da38257..8d2e939da032 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -23,6 +23,7 @@ import android.util.AttributeSet import android.util.StateSet import android.view.Gravity import android.view.View +import android.view.ViewConfiguration import android.view.ViewGroup import android.view.accessibility.AccessibilityNodeInfo import android.widget.FrameLayout @@ -31,6 +32,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import com.airbnb.lottie.LottieCompositionFactory import com.airbnb.lottie.LottieDrawable import com.android.systemui.common.ui.view.LongPressHandlingView +import com.android.systemui.log.LongPressHandlingViewLogger import com.android.systemui.res.R class DeviceEntryIconView @@ -39,8 +41,17 @@ constructor( context: Context, attrs: AttributeSet?, defStyleAttrs: Int = 0, + logger: LongPressHandlingViewLogger? = null, ) : FrameLayout(context, attrs, defStyleAttrs) { - val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) + + val longPressHandlingView: LongPressHandlingView = + LongPressHandlingView( + context = context, + attrs = attrs, + longPressDuration = { ViewConfiguration.getLongPressTimeout().toLong() }, + allowedTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(), + logger = logger, + ) val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } val aodFpDrawable: LottieDrawable = LottieDrawable() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 51230dd0a47c..782d37b1929c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -42,6 +42,9 @@ import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LongPressHandlingViewLogger +import com.android.systemui.log.dagger.LongPressTouchLog import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -69,6 +72,7 @@ constructor( private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>, private val falsingManager: Lazy<FalsingManager>, private val vibratorHelper: Lazy<VibratorHelper>, + @LongPressTouchLog private val logBuffer: LogBuffer, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view private var disposableHandle: DisposableHandle? = null @@ -88,7 +92,16 @@ constructor( val view = if (DeviceEntryUdfpsRefactor.isEnabled) { - DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId } + DeviceEntryIconView( + context, + null, + logger = + LongPressHandlingViewLogger( + logBuffer = logBuffer, + TAG + ) + ) + .apply { id = deviceEntryIconViewId } } else { // KeyguardBottomAreaRefactor.isEnabled or MigrateClocksToBlueprint.isEnabled LockIconView(context, null).apply { id = R.id.lock_icon_view } @@ -258,4 +271,8 @@ constructor( } } } + + companion object { + private const val TAG = "DefaultDeviceEntrySection" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt index b432417802c9..9f8e9c575a75 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerDependencies.kt @@ -18,6 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LongPressHandlingViewLogger +import com.android.systemui.log.dagger.LongPressTouchLog import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.statusbar.gesture.TapGestureDetector import dagger.Lazy @@ -37,4 +40,11 @@ constructor( Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>, val messageAreaViewModel: AlternateBouncerMessageAreaViewModel, val powerInteractor: PowerInteractor, -) + @LongPressTouchLog private val touchLogBuffer: LogBuffer, +) { + val logger: LongPressHandlingViewLogger = + LongPressHandlingViewLogger(logBuffer = touchLogBuffer, TAG) + companion object { + private const val TAG = "AlternateBouncer" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt b/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.kt new file mode 100644 index 000000000000..4ff81184d045 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/LongPressHandlingViewLogger.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.log + +import com.android.systemui.log.core.LogLevel.DEBUG +import com.google.errorprone.annotations.CompileTimeConstant + +data class LongPressHandlingViewLogger +constructor( + private val logBuffer: LogBuffer, + @CompileTimeConstant private val tag: String = "LongPressHandlingViewLogger" +) { + fun schedulingLongPress(delay: Long) { + logBuffer.log( + tag, + DEBUG, + { long1 = delay }, + { "on MotionEvent.Down: scheduling long press activation after $long1 ms" } + ) + } + + fun longPressTriggered() { + logBuffer.log(tag, DEBUG, "long press event detected and dispatched") + } + + fun motionEventCancelled() { + logBuffer.log(tag, DEBUG, "Long press may be cancelled due to MotionEventModel.Cancel") + } + + fun dispatchingSingleTap() { + logBuffer.log(tag, DEBUG, "Dispatching single tap instead of long press") + } + + fun onUpEvent(distanceMoved: Float, touchSlop: Int, gestureDuration: Long) { + logBuffer.log( + tag, + DEBUG, + { + double1 = distanceMoved.toDouble() + int1 = touchSlop + long1 = gestureDuration + }, + { + "on MotionEvent.Up: distanceMoved: $double1, " + + "allowedTouchSlop: $int1, " + + "eventDuration: $long1" + } + ) + } + + fun cancelingLongPressDueToTouchSlop(distanceMoved: Float, allowedTouchSlop: Int) { + logBuffer.log( + tag, + DEBUG, + { + double1 = distanceMoved.toDouble() + int1 = allowedTouchSlop + }, + { + "on MotionEvent.Motion: May cancel long press due to movement: " + + "distanceMoved: $double1, " + + "allowedTouchSlop: $int1 " + } + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 19906fdc4d5f..498c34c03f2d 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -717,4 +717,12 @@ public class LogModule { public static LogBuffer provideVolumeLogBuffer(LogBufferFactory factory) { return factory.create("VolumeLog", 50); } + + /** Provides a {@link LogBuffer} for use by long touch event handlers. */ + @Provides + @SysUISingleton + @LongPressTouchLog + public static LogBuffer providesLongPressTouchLog(LogBufferFactory factory) { + return factory.create("LongPressViewLog", 200); + } } diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt new file mode 100644 index 000000000000..1163d74b62a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LongPressTouchLog.kt @@ -0,0 +1,25 @@ +/* + * 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.log.dagger + +import javax.inject.Qualifier + +/** Log buffer for logging touch/long press events */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class LongPressTouchLog diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java index b96e83d43e32..f723ff264e0c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_QS_TILE; + import android.content.Intent; import android.os.Handler; import android.os.Looper; @@ -96,7 +98,7 @@ public class HearingDevicesTile extends QSTileImpl<BooleanState> { @Override protected void handleClick(@Nullable Expandable expandable) { - mUiHandler.post(() -> mDialogManager.showDialog(expandable)); + mUiHandler.post(() -> mDialogManager.showDialog(expandable, LAUNCH_SOURCE_QS_TILE)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index a3feb2b09da3..d89e73d2c69f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -43,12 +43,14 @@ import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.recordissue.IssueRecordingService +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC import com.android.systemui.recordissue.TraceurMessageSender import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.RecordingService import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil @@ -56,6 +58,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import java.util.concurrent.Executor import javax.inject.Inject +const val DELAY_MS: Long = 0 +const val INTERVAL_MS: Long = 1000 + class RecordIssueTile @Inject constructor( @@ -77,6 +82,7 @@ constructor( @Background private val bgExecutor: Executor, private val issueRecordingState: IssueRecordingState, private val delegateFactory: RecordIssueDialogDelegate.Factory, + private val recordingController: RecordingController, ) : QSTileImpl<QSTile.BooleanState>( host, @@ -132,23 +138,25 @@ constructor( } private fun startIssueRecordingService() = - PendingIntent.getForegroundService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStartIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + recordingController.startCountdown( + DELAY_MS, + INTERVAL_MS, + pendingServiceIntent(getStartIntent(userContextProvider.userContext)), + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) + ) private fun stopIssueRecordingService() = - PendingIntent.getService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStopIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + private fun pendingServiceIntent(action: Intent) = + PendingIntent.getService( + userContextProvider.userContext, + RecordingService.REQUEST_CODE, + action, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + private fun showPrompt(expandable: Expandable?) { val dialog: AlertDialog = delegateFactory diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt index 4971fefc8f57..0c8a3750f6fe 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingUserActionInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.impl.irecording import android.app.AlertDialog import android.app.BroadcastOptions import android.app.PendingIntent +import android.content.Intent import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj @@ -27,12 +28,16 @@ import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.qs.tiles.DELAY_MS +import com.android.systemui.qs.tiles.INTERVAL_MS import com.android.systemui.qs.tiles.base.interactor.QSTileInput import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction -import com.android.systemui.recordissue.IssueRecordingService +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStartIntent +import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopIntent import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.RecordingService import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil @@ -53,6 +58,7 @@ constructor( private val panelInteractor: PanelInteractor, private val userContextProvider: UserContextProvider, private val delegateFactory: RecordIssueDialogDelegate.Factory, + private val recordingController: RecordingController, ) : QSTileUserActionInteractor<IssueRecordingModel> { override suspend fun handleInput(input: QSTileInput<IssueRecordingModel>) { @@ -95,20 +101,22 @@ constructor( } private fun startIssueRecordingService() = - PendingIntent.getForegroundService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStartIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + recordingController.startCountdown( + DELAY_MS, + INTERVAL_MS, + pendingServiceIntent(getStartIntent(userContextProvider.userContext)), + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) + ) private fun stopIssueRecordingService() = - PendingIntent.getService( - userContextProvider.userContext, - RecordingService.REQUEST_CODE, - IssueRecordingService.getStopIntent(userContextProvider.userContext), - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + pendingServiceIntent(getStopIntent(userContextProvider.userContext)) .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) + + private fun pendingServiceIntent(action: Intent) = + PendingIntent.getService( + userContextProvider.userContext, + RecordingService.REQUEST_CODE, + action, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index c2d112edd65d..483373d8fb6d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -20,13 +20,16 @@ import android.app.Flags import android.content.Context import android.os.UserHandle import com.android.app.tracing.coroutines.flow.map +import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.tiles.ModesTile import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes +import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow @@ -61,28 +64,39 @@ constructor( suspend fun getCurrentTileModel() = buildTileData(zenModeInteractor.getActiveModes()) private fun buildTileData(activeModes: ActiveZenModes): ModesTileModel { - val modesIconResId = com.android.internal.R.drawable.ic_zen_priority_modes - if (usesModeIcons()) { - val mainModeDrawable = activeModes.mainMode?.icon?.drawable - val iconResId = if (mainModeDrawable == null) modesIconResId else null - + val tileIcon = getTileIcon(activeModes.mainMode) return ModesTileModel( isActivated = activeModes.isAnyActive(), - icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(), - iconResId = iconResId, + icon = tileIcon.icon, + iconResId = tileIcon.resId, activeModes = activeModes.modeNames ) } else { return ModesTileModel( isActivated = activeModes.isAnyActive(), - icon = context.getDrawable(modesIconResId)!!.asIcon(), - iconResId = modesIconResId, + icon = context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), + iconResId = ModesTile.ICON_RES_ID, activeModes = activeModes.modeNames ) } } + private data class TileIcon(val icon: Icon.Loaded, val resId: Int?) + + private fun getTileIcon(activeMode: ZenModeInfo?): TileIcon { + return if (activeMode != null) { + // ZenIconKey.resPackage is null if its resId is a system icon. + if (activeMode.icon.key.resPackage == null) { + TileIcon(activeMode.icon.drawable.asIcon(), activeMode.icon.key.resId) + } else { + TileIcon(activeMode.icon.drawable.asIcon(), null) + } + } else { + TileIcon(context.getDrawable(ModesTile.ICON_RES_ID)!!.asIcon(), ModesTile.ICON_RES_ID) + } + } + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi()) private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt index 7f571b135fc8..69da3134314b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt @@ -36,9 +36,7 @@ constructor( ) : QSTileDataToStateMapper<ModesTileModel> { override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - if (!android.app.Flags.modesUiIcons()) { - iconRes = data.iconResId - } + iconRes = data.iconResId icon = { data.icon } activationState = if (data.isActivated) { 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 e251c9edb57f..98907b037d85 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 @@ -22,7 +22,9 @@ import android.app.StatusBarManager import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.internal.logging.UiEventLogger +import com.android.keyguard.AuthInteractionProperties import com.android.systemui.CoreStartable +import com.android.systemui.Flags import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor @@ -73,6 +75,8 @@ 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 com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.MSDLPlayer import dagger.Lazy import java.io.PrintWriter import java.util.Optional @@ -139,10 +143,13 @@ constructor( private val statusBarStateController: SysuiStatusBarStateController, private val alternateBouncerInteractor: AlternateBouncerInteractor, private val vibratorHelper: VibratorHelper, + private val msdlPlayer: MSDLPlayer, ) : CoreStartable { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() + private val authInteractionProperties = AuthInteractionProperties() + override fun start() { if (SceneContainerFlag.isEnabled) { sceneLogger.logFrameworkEnabled(isEnabled = true) @@ -541,9 +548,16 @@ constructor( deviceEntryHapticsInteractor.playSuccessHaptic .sample(sceneInteractor.currentScene) .collect { currentScene -> - vibratorHelper.vibrateAuthSuccess( - "$TAG, $currentScene device-entry::success" - ) + if (Flags.msdlFeedback()) { + msdlPlayer.playToken( + MSDLToken.UNLOCK, + authInteractionProperties, + ) + } else { + vibratorHelper.vibrateAuthSuccess( + "$TAG, $currentScene device-entry::success" + ) + } } } @@ -551,9 +565,16 @@ constructor( deviceEntryHapticsInteractor.playErrorHaptic .sample(sceneInteractor.currentScene) .collect { currentScene -> - vibratorHelper.vibrateAuthError( - "$TAG, $currentScene device-entry::error" - ) + if (Flags.msdlFeedback()) { + msdlPlayer.playToken( + MSDLToken.FAILURE, + authInteractionProperties, + ) + } else { + vibratorHelper.vibrateAuthError( + "$TAG, $currentScene device-entry::error" + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java index 64dfc6c9eb3c..735b4c34f86f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/VibratorHelper.java @@ -54,6 +54,8 @@ public class VibratorHelper { VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK); private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + private static final VibrationAttributes COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES = + VibrationAttributes.createForUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST); private final Executor mExecutor; @@ -151,7 +153,7 @@ public class VibratorHelper { vibrate(Process.myUid(), "com.android.systemui", BIOMETRIC_SUCCESS_VIBRATION_EFFECT, reason, - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES); } /** @@ -160,7 +162,7 @@ public class VibratorHelper { public void vibrateAuthError(String reason) { vibrate(Process.myUid(), "com.android.systemui", BIOMETRIC_ERROR_VIBRATION_EFFECT, reason, - HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES); + COMMUNICATION_REQUEST_VIBRATION_ATTRIBUTES); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt index 2c462b7329b4..77c4130482c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt @@ -198,12 +198,22 @@ constructor( parentView, /* attachToRoot= */ false ) as EnRouteView - InflatedContentViewHolder(newView) { EnRouteViewBinder.bindWhileAttached(newView, createViewModel()) } } - RichOngoingNotificationViewType.Expanded, + RichOngoingNotificationViewType.Expanded -> { + val newView = + LayoutInflater.from(systemUiContext) + .inflate( + R.layout.notification_template_en_route_expanded, + parentView, + /* attachToRoot= */ false + ) as EnRouteView + InflatedContentViewHolder(newView) { + EnRouteViewBinder.bindWhileAttached(newView, createViewModel()) + } + } RichOngoingNotificationViewType.HeadsUp -> NullContentView } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 7c0178436268..8ff1ab640442 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -3691,6 +3691,10 @@ public class NotificationStackScrollLayout if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: { + // If scene container is active, NSSL should not control its own scrolling. + if (SceneContainerFlag.isEnabled()) { + return false; + } if (!mIsBeingDragged) { final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); if (vscroll != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 43f9af6016f1..dd4b0005b034 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -992,7 +992,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } else { showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset); } - if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing) { + if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing && isBouncerShowing()) { hideAlternateBouncer(true); mDismissCallbackRegistry.notifyDismissCancelled(); mPrimaryBouncerInteractor.setDismissAction(null, null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt index 85bbe7e53493..d6013192f55e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt @@ -100,7 +100,14 @@ fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false) val spn = if (statusBarSwitchToSpnFromDataSpn()) { - getStringExtra(EXTRA_SPN) + // Context: b/358669494. Use DATA_SPN if it exists, since that allows carriers to + // customize the display name. Otherwise, fall back to the SPN + val dataSpn = getStringExtra(EXTRA_DATA_SPN) + if (dataSpn.isNullOrEmpty()) { + getStringExtra(EXTRA_SPN) + } else { + dataSpn + } } else { getStringExtra(EXTRA_DATA_SPN) } @@ -109,10 +116,8 @@ fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? { val plmn = getStringExtra(EXTRA_PLMN) val str = StringBuilder() - val strData = StringBuilder() if (showPlmn && plmn != null) { str.append(plmn) - strData.append(plmn) } if (showSpn && spn != null) { if (str.isNotEmpty()) { diff --git a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt index 4b0e5d188ffa..6d99183dec33 100644 --- a/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/telephony/domain/interactor/TelephonyInteractor.kt @@ -29,11 +29,12 @@ import kotlinx.coroutines.flow.StateFlow class TelephonyInteractor @Inject constructor( - repository: TelephonyRepository, + private val repository: TelephonyRepository, ) { @Annotation.CallState val callState: Flow<Int> = repository.callState val isInCall: StateFlow<Boolean> = repository.isInCall - val hasTelephonyRadio: Boolean = repository.hasTelephonyRadio + val hasTelephonyRadio: Boolean + get() = repository.hasTelephonyRadio } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index 8934d8f8a954..d9e72bf592a0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -1305,13 +1305,12 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, STREAM_UNKNOWN); - final int level = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1); final int oldLevel = intent .getIntExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, -1); if (D.BUG) Log.d(TAG, "onReceive VOLUME_CHANGED_ACTION stream=" + stream - + " level=" + level + " oldLevel=" + oldLevel); + + " oldLevel=" + oldLevel); if (stream != STREAM_UNKNOWN) { - changed = updateStreamLevelW(stream, level); + changed |= onVolumeChangedW(stream, 0); } } else if (action.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) { final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt index 43a780357027..c42e25b20e0d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt @@ -29,7 +29,7 @@ import com.android.internal.logging.MetricsLogger import com.android.internal.widget.LockPatternUtils import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.haptics.msdl.FakeMSDLPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.shade.ShadeController import com.android.systemui.statusbar.policy.ConfigurationController @@ -72,7 +72,7 @@ class EmergencyButtonControllerTest : SysuiTestCase() { val mainExecutor = FakeExecutor(fakeSystemClock) val backgroundExecutor = FakeExecutor(fakeSystemClock) private val kosmos = testKosmos() - private val msdlPlayer: FakeMSDLPlayer = kosmos.msdlPlayer + private val msdlPlayer = kosmos.fakeMSDLPlayer lateinit var underTest: EmergencyButtonController diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index d3b7d2207854..662815ee7cbe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -52,7 +52,6 @@ import android.widget.Spinner; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.internal.logging.UiEventLogger; import com.android.settingslib.bluetooth.BluetoothEventManager; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; @@ -92,6 +91,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); + private static final int TEST_LAUNCH_SOURCE_ID = 1; private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"; private static final String DEVICE_NAME = "test_name"; private static final String TEST_PKG = "pkg"; @@ -124,7 +124,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { @Mock private AudioManager mAudioManager; @Mock - private UiEventLogger mUiEventLogger; + private HearingDevicesUiEventLogger mUiEventLogger; @Mock private CachedBluetoothDevice mCachedDevice; @Mock @@ -182,7 +182,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { anyInt(), any()); assertThat(intentCaptor.getValue().getAction()).isEqualTo( Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS); - verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR); + verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR, + TEST_LAUNCH_SOURCE_ID); } @Test @@ -196,7 +197,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { anyInt(), any()); assertThat(intentCaptor.getValue().getAction()).isEqualTo( HearingDevicesDialogDelegate.ACTION_BLUETOOTH_DEVICE_DETAILS); - verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK); + verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK, + TEST_LAUNCH_SOURCE_ID); } @Test @@ -207,7 +209,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext)); verify(mCachedDevice).disconnect(); - verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT); + verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT, + TEST_LAUNCH_SOURCE_ID); } @Test @@ -304,6 +307,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialogDelegate = new HearingDevicesDialogDelegate( mContext, true, + TEST_LAUNCH_SOURCE_ID, mDialogFactory, mActivityStarter, mDialogTransitionAnimator, @@ -327,6 +331,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialogDelegate = new HearingDevicesDialogDelegate( mContext, false, + TEST_LAUNCH_SOURCE_ID, mDialogFactory, mActivityStarter, mDialogTransitionAnimator, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 1e2369034bf7..7889b3cd6cc3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -61,10 +61,12 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.display.data.repository.FakeDisplayRepository +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.events.ANIMATING_OUT +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -145,6 +147,9 @@ open class AuthContainerViewTest : SysuiTestCase() { private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) + private val kosmos = testKosmos() + private val msdlPlayer = kosmos.msdlPlayer + private var authContainer: TestAuthContainerView? = null @Before @@ -668,7 +673,8 @@ open class AuthContainerViewTest : SysuiTestCase() { { credentialViewModel }, fakeExecutor, vibrator, - lazyViewCapture + lazyViewCapture, + msdlPlayer, ) { override fun postOnAnimation(runnable: Runnable) { runnable.run() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 4fc41669b2c9..55fd3440ea07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -37,12 +37,15 @@ import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.HapticFeedbackConstants import android.view.MotionEvent import android.view.Surface import androidx.test.filters.SmallTest import com.android.app.activityTaskManager +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.Utils.toBitmap @@ -72,6 +75,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.util.mockito.withArgCaptor +import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first @@ -124,6 +128,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa private val defaultLogoDescriptionFromActivityInfo = "Test Coke App" private val logoDescriptionFromApp = "Test Cake App" private val packageNameForLogoWithOverrides = "should.use.overridden.logo" + private val authInteractionProperties = AuthInteractionProperties() /** Prompt panel size padding */ private val smallHorizontalGuidelinePadding = @@ -707,31 +712,66 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun set_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) - val confirmHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(confirmHaptics?.hapticFeedbackConstant) - .isEqualTo( - if (expectConfirmation) HapticFeedbackConstants.NO_HAPTICS - else HapticFeedbackConstants.BIOMETRIC_CONFIRM - ) - assertThat(confirmHaptics?.flag).isNull() + val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + if (expectConfirmation) { + assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None) + } else { + val confirmHaptics = + hapticsPreConfirm as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(confirmHaptics.constant) + .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(confirmHaptics.flag).isNull() + } if (expectConfirmation) { kosmos.promptViewModel.confirmAuthenticated() } - val confirmedHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(confirmedHaptics?.hapticFeedbackConstant) + val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val confirmedHaptics = + hapticsPostConfirm as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(confirmedHaptics.constant) .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) - assertThat(confirmedHaptics?.flag).isNull() + assertThat(confirmedHaptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun set_msdl_haptic_on_confirm_when_confirmation_required_otherwise_on_authenticated() = + runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + + kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) + + val hapticsPreConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + + if (expectConfirmation) { + assertThat(hapticsPreConfirm).isEqualTo(PromptViewModel.HapticsToPlay.None) + } else { + val confirmHaptics = hapticsPreConfirm as PromptViewModel.HapticsToPlay.MSDL + assertThat(confirmHaptics.token).isEqualTo(MSDLToken.UNLOCK) + assertThat(confirmHaptics.properties).isEqualTo(authInteractionProperties) + } + + if (expectConfirmation) { + kosmos.promptViewModel.confirmAuthenticated() + } + + val hapticsPostConfirm by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val confirmedHaptics = hapticsPostConfirm as PromptViewModel.HapticsToPlay.MSDL + assertThat(confirmedHaptics.token).isEqualTo(MSDLToken.UNLOCK) + assertThat(confirmedHaptics.properties).isEqualTo(authInteractionProperties) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playSuccessHaptic_SetsConfirmConstant() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) @@ -740,20 +780,48 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa kosmos.promptViewModel.confirmAuthenticated() } - val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(currentHaptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) - assertThat(currentHaptics?.flag).isNull() + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(currentHaptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playSuccessHaptic_SetsUnlockMSDLFeedback() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 1_000L) + + if (expectConfirmation) { + kosmos.promptViewModel.confirmAuthenticated() + } + + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL + assertThat(currentHaptics.token).isEqualTo(MSDLToken.UNLOCK) + assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun playErrorHaptic_SetsRejectConstant() = runGenericTest { kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false) - val currentHaptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(currentHaptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) - assertThat(currentHaptics?.flag).isNull() + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(currentHaptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(currentHaptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun playErrorHaptic_SetsFailureMSDLFeedback() = runGenericTest { + kosmos.promptViewModel.showTemporaryError("test", "messageAfterError", false) + + val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val currentHaptics = haptics as PromptViewModel.HapticsToPlay.MSDL + assertThat(currentHaptics.token).isEqualTo(MSDLToken.FAILURE) + assertThat(currentHaptics.properties).isEqualTo(authInteractionProperties) } // biometricprompt_sfps_fingerprint_authenticating reused across rotations @@ -855,6 +923,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun set_haptic_on_errors() = runGenericTest { kosmos.promptViewModel.showTemporaryError( "so sad", @@ -863,13 +932,30 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa hapticFeedback = true, ) - val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(haptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) - assertThat(haptics?.flag).isNull() + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant + assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(haptics.flag).isNull() + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun set_msdl_haptic_on_errors() = runGenericTest { + kosmos.promptViewModel.showTemporaryError( + "so sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = true, + ) + + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL + assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE) + assertThat(haptics.properties).isEqualTo(authInteractionProperties) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun plays_haptic_on_errors_unless_skipped() = runGenericTest { kosmos.promptViewModel.showTemporaryError( "still sad", @@ -878,11 +964,26 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa hapticFeedback = false, ) - val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) - assertThat(haptics?.hapticFeedbackConstant).isEqualTo(HapticFeedbackConstants.NO_HAPTICS) + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None) + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun plays_msdl_haptic_on_errors_unless_skipped() = runGenericTest { + kosmos.promptViewModel.showTemporaryError( + "still sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = false, + ) + + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + assertThat(hapticsToPlay).isEqualTo(PromptViewModel.HapticsToPlay.None) } @Test + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) fun plays_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest { val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0) @@ -894,15 +995,37 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa hapticFeedback = true, ) - val haptics by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.HapticConstant if (expectConfirmation) { - assertThat(haptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) - assertThat(haptics?.flag).isNull() + assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_REJECT) + assertThat(haptics.flag).isNull() } else { - assertThat(haptics?.hapticFeedbackConstant) - .isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + assertThat(haptics.constant).isEqualTo(HapticFeedbackConstants.BIOMETRIC_CONFIRM) + } + } + + @Test + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + fun plays_msdl_haptic_on_error_after_auth_when_confirmation_needed() = runGenericTest { + val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false) + kosmos.promptViewModel.showAuthenticated(testCase.authenticatedModality, 0) + + kosmos.promptViewModel.showTemporaryError( + "still sad", + messageAfterError = "", + authenticateAfterError = false, + hapticFeedback = true, + ) + + val hapticsToPlay by collectLastValue(kosmos.promptViewModel.hapticsToPlay) + val haptics = hapticsToPlay as PromptViewModel.HapticsToPlay.MSDL + if (expectConfirmation) { + assertThat(haptics.token).isEqualTo(MSDLToken.FAILURE) + } else { + assertThat(haptics.token).isEqualTo(MSDLToken.UNLOCK) } + assertThat(haptics.properties).isEqualTo(authInteractionProperties) } private suspend fun TestScope.showTemporaryErrors( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index 7cc91853a749..bfb8a57e6271 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView @@ -86,6 +87,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { { mock(DeviceEntryBackgroundViewModel::class.java) }, { falsingManager }, { mock(VibratorHelper::class.java) }, + logcatLogBuffer(), ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java index 76c8cf081262..7d41a20e628f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.LAUNCH_SOURCE_QS_TILE; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -151,7 +153,7 @@ public class HearingDevicesTileTest extends SysuiTestCase { mTile.handleClick(expandable); mTestableLooper.processAllMessages(); - verify(mHearingDevicesDialogManager).showDialog(expandable); + verify(mHearingDevicesDialogManager).showDialog(expandable, LAUNCH_SOURCE_QS_TILE); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index 73548baad377..ca518f9bc5d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.recordissue.TraceurMessageSender import com.android.systemui.res.R +import com.android.systemui.screenrecord.RecordingController import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.phone.SystemUIDialog @@ -65,6 +66,7 @@ class RecordIssueTileTest : SysuiTestCase() { @Mock private lateinit var qsEventLogger: QsEventLogger @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var recordingController: RecordingController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil @@ -109,6 +111,7 @@ class RecordIssueTileTest : SysuiTestCase() { Executors.newSingleThreadExecutor(), issueRecordingState, delegateFactory, + recordingController, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 3e3c046ce62e..01a3d36a05ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -779,6 +779,26 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test @DisableSceneContainer + public void testResetDoesNotHideBouncerWhenNotShowing() { + reset(mDismissCallbackRegistry); + reset(mPrimaryBouncerInteractor); + + // GIVEN the keyguard is showing + reset(mAlternateBouncerInteractor); + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); + + // WHEN SBKV is reset with hideBouncerWhenShowing=true + mStatusBarKeyguardViewManager.reset(true); + + // THEN no calls to hide should be made + verify(mAlternateBouncerInteractor, never()).hide(); + verify(mDismissCallbackRegistry, never()).notifyDismissCancelled(); + verify(mPrimaryBouncerInteractor, never()).setDismissAction(eq(null), eq(null)); + } + + @Test + @DisableSceneContainer public void testResetHideBouncerWhenShowing_alternateBouncerHides() { reset(mDismissCallbackRegistry); reset(mPrimaryBouncerInteractor); @@ -786,6 +806,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { // GIVEN the keyguard is showing reset(mAlternateBouncerInteractor); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true); // WHEN SBKV is reset with hideBouncerWhenShowing=true mStatusBarKeyguardViewManager.reset(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt index fe408e3246c8..763449028f28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt @@ -817,7 +817,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { captor.lastValue.onReceive(context, intent) // spnIntent() sets all values to true and test strings - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) job.cancel() } @@ -852,7 +852,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { verify(context).registerReceiver(captor.capture(), any()) captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) // WHEN an intent with a different subId is sent val wrongSubIntent = spnIntent(subId = 101) @@ -860,7 +860,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { captor.lastValue.onReceive(context, wrongSubIntent) // THEN the previous intent's name is still used - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) job.cancel() } @@ -902,7 +902,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { verify(context).registerReceiver(captor.capture(), any()) captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) val intentWithoutInfo = spnIntent( @@ -961,7 +961,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { // The value is still there despite no active subscribers assertThat(underTest.networkName.value) - .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) + .isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) } @Test @@ -986,7 +986,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) - fun networkName_allFieldsSet_doesNotUseDataSpn() = + fun networkName_allFieldsSet_prioritizesDataSpnOverSpn() = testScope.runTest { val latest by collectLastValue(underTest.networkName) val captor = argumentCaptor<BroadcastReceiver>() @@ -1002,6 +1002,27 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { plmn = PLMN, ) captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_spnAndPlmn_fallbackToSpnWhenNullDataSpn() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = null, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) } @@ -1043,7 +1064,27 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { plmn = PLMN, ) captor.lastValue.onReceive(context, intent) - assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN")) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNotNull_showSpn_spnNotNull_dataSpnNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = null, + showPlmn = true, + plmn = PLMN, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$PLMN$SEP$SPN")) } @Test @@ -1102,10 +1143,50 @@ class MobileConnectionRepositoryTest : SysuiTestCase() { plmn = null, ) captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$DATA_SPN")) + } + + @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn_dataSpnNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = SPN, + dataSpn = null, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived("$SPN")) } @Test + @EnableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) + fun networkName_showPlmn_plmnNull_showSpn_bothSpnNull() = + testScope.runTest { + val latest by collectLastValue(underTest.networkName) + val captor = argumentCaptor<BroadcastReceiver>() + verify(context).registerReceiver(captor.capture(), any()) + val intent = + spnIntent( + subId = SUB_1_ID, + showSpn = true, + spn = null, + dataSpn = null, + showPlmn = true, + plmn = null, + ) + captor.lastValue.onReceive(context, intent) + assertThat(latest).isEqualTo(DEFAULT_NAME_MODEL) + } + + @Test @DisableFlags(Flags.FLAG_STATUS_BAR_SWITCH_TO_SPN_FROM_DATA_SPN) fun networkName_showPlmn_plmnNull_showSpn_flagOff() = testScope.runTest { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java index 035847497178..3041240e8c86 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java @@ -19,6 +19,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.UserHandle; @@ -40,6 +41,7 @@ public class SysuiTestableContext extends TestableContext { @GuardedBy("mRegisteredReceivers") private final Set<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>(); private final Map<UserHandle, Context> mContextForUser = new HashMap<>(); + private final Map<String, Context> mContextForPackage = new HashMap<>(); public SysuiTestableContext(Context base) { super(base); @@ -175,4 +177,22 @@ public class SysuiTestableContext extends TestableContext { } return super.createContextAsUser(user, flags); } + + /** + * Sets a Context object that will be returned as the result of {@link #createPackageContext} + * for a specific {@code packageName}. + */ + public void prepareCreatePackageContext(String packageName, Context context) { + mContextForPackage.put(packageName, context); + } + + @Override + public Context createPackageContext(String packageName, int flags) + throws PackageManager.NameNotFoundException { + Context packageContext = mContextForPackage.get(packageName); + if (packageContext != null) { + return packageContext; + } + return super.createPackageContext(packageName, flags); + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt index 5ced578ad974..3087d01a2479 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt @@ -26,6 +26,7 @@ import com.android.systemui.bouncer.data.repository.emergencyServicesRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository import com.android.systemui.telephony.domain.interactor.telephonyInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor @@ -50,5 +51,6 @@ val Kosmos.bouncerActionButtonInteractor by Fixture { }, metricsLogger = metricsLogger, dozeLogger = mock(), + sceneInteractor = { sceneInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt index f5a05b44d2cf..4f5c32abd2f8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/MSDLPlayerKosmos.kt @@ -17,5 +17,7 @@ package com.android.systemui.haptics.msdl import com.android.systemui.kosmos.Kosmos +import com.google.android.msdl.domain.MSDLPlayer -val Kosmos.msdlPlayer by Kosmos.Fixture { FakeMSDLPlayer() } +var Kosmos.msdlPlayer: MSDLPlayer by Kosmos.Fixture { fakeMSDLPlayer } +val Kosmos.fakeMSDLPlayer by Kosmos.Fixture { FakeMSDLPlayer() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt index 1e95fc12bdb5..740d8919cbc0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderKosmos.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerWindowViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.logcatLogBuffer import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.mockito.mock @@ -64,6 +65,7 @@ private val Kosmos.alternateBouncerDependencies by }, messageAreaViewModel = mock<AlternateBouncerMessageAreaViewModel>(), powerInteractor = powerInteractor, + touchLogBuffer = logcatLogBuffer(), ) } 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 851a378f3165..457bd284ea8d 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 @@ -36,7 +36,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.globalactions.domain.interactor.globalActionsInteractor -import com.android.systemui.haptics.msdl.msdlPlayer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.haptics.qs.qsLongPressEffect import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -155,5 +155,5 @@ class KosmosJavaAdapter() { val scrimController by lazy { kosmos.scrimController } val scrimStartable by lazy { kosmos.scrimStartable } val sceneContainerOcclusionInteractor by lazy { kosmos.sceneContainerOcclusionInteractor } - val msdlPlayer by lazy { kosmos.msdlPlayer } + val msdlPlayer by lazy { kosmos.fakeMSDLPlayer } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index b612a8b5893a..9a5698cfb8ca 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -27,6 +27,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInt import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor @@ -86,5 +87,6 @@ val Kosmos.sceneContainerStartable by Fixture { statusBarStateController = sysuiStatusBarStateController, alternateBouncerInteractor = alternateBouncerInteractor, vibratorHelper = vibratorHelper, + msdlPlayer = msdlPlayer, ) } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 9b0c8e554d64..333fe4c8147f 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -127,6 +127,7 @@ java_library { libs: [ "framework-minus-apex.ravenwood", "ravenwood-junit", + "ravenwood-helper-libcore-runtime", ], visibility: ["//visibility:private"], } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java index f237ba908507..1d182da5e7fd 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java @@ -27,6 +27,9 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.os.RuntimeInit; +import com.android.ravenwood.common.RavenwoodCommonUtils; + import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runners.model.TestClass; @@ -35,7 +38,7 @@ import org.junit.runners.model.TestClass; * Provide hook points created by {@link RavenwoodAwareTestRunner}. */ public class RavenwoodAwareTestRunnerHook { - private static final String TAG = "RavenwoodAwareTestRunnerHook"; + private static final String TAG = RavenwoodAwareTestRunner.TAG; private RavenwoodAwareTestRunnerHook() { } @@ -56,20 +59,36 @@ public class RavenwoodAwareTestRunnerHook { * Called when a runner starts, before the inner runner gets a chance to run. */ public static void onRunnerInitializing(Runner runner, TestClass testClass) { + // TODO: Move the initialization code to a better place. + + initOnce(); + // This log call also ensures the framework JNI is loaded. Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass() + " runner=" + runner); - // TODO: Move the initialization code to a better place. + // This is needed to make AndroidJUnit4ClassRunner happy. + InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); + } + + private static boolean sInitialized = false; + + private static void initOnce() { + if (sInitialized) { + return; + } + sInitialized = true; + + // We haven't initialized liblog yet, so directly write to System.out here. + RavenwoodCommonUtils.log(TAG, "initOnce()"); + + // Redirect stdout/stdin to liblog. + RuntimeInit.redirectLogStreams(); // This will let AndroidJUnit4 use the original runner. System.setProperty("android.junit.runner", "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); - - - // This is needed to make AndroidJUnit4ClassRunner happy. - InstrumentationRegistry.registerInstance(null, Bundle.EMPTY); } /** @@ -87,7 +106,7 @@ public class RavenwoodAwareTestRunnerHook { */ public static boolean onBefore(RavenwoodAwareTestRunner runner, Description description, Scope scope, Order order) { - Log.i(TAG, "onBefore: description=" + description + ", " + scope + ", " + order); + Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order); if (scope == Scope.Class && order == Order.First) { // Keep track of the current class. @@ -113,7 +132,7 @@ public class RavenwoodAwareTestRunnerHook { */ public static boolean onAfter(RavenwoodAwareTestRunner runner, Description description, Scope scope, Order order, Throwable th) { - Log.i(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th); + Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th); if (scope == Scope.Instance && order == Order.First) { getStats().onTestFinished(sCurrentClassDescription, description, diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java index a2088fd0b77f..0059360a0a29 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java @@ -36,7 +36,6 @@ import android.view.DisplayAdjustments; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.os.RuntimeInit; import com.android.server.LocalServices; import org.junit.runner.Description; @@ -92,8 +91,6 @@ public class RavenwoodRuleImpl { Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler); } - RuntimeInit.redirectLogStreams(); - android.os.Process.init$ravenwood(rule.mUid, rule.mPid); android.os.Binder.init$ravenwood(); setSystemProperties(rule.mSystemProperties); diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 7d991663f4b1..bfde9cb7099e 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -23,7 +23,6 @@ import static java.lang.annotation.ElementType.TYPE; import android.util.Log; -import com.android.ravenwood.common.RavenwoodCommonUtils; import com.android.ravenwood.common.SneakyThrow; import org.junit.Assume; @@ -75,7 +74,7 @@ import java.lang.reflect.InvocationTargetException; * (no hooks, etc.) */ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orderable { - private static final String TAG = "RavenwoodAwareTestRunner"; + public static final String TAG = "Ravenwood"; @Inherited @Target({TYPE}) @@ -142,16 +141,9 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde private Description mDescription = null; private Throwable mExceptionInConstructor = null; - /** Simple logging method. */ - private void log(String message) { - RavenwoodCommonUtils.log(TAG, "[" + getTestClass().getJavaClass() + " @" + this + "] " - + message); - } - - private Error logAndFail(String message, Throwable innerException) { - log(message); - log(" Exception=" + innerException); - throw new AssertionError(message, innerException); + private Error logAndFail(String message, Throwable exception) { + Log.e(TAG, message, exception); + throw new AssertionError(message, exception); } public TestClass getTestClass() { @@ -165,6 +157,8 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde try { mTestClass = new TestClass(testClass); + onRunnerInitializing(); + /* * If the class has @DisabledOnRavenwood, then we'll delegate to * ClassSkippingTestRunner, which simply skips it. @@ -186,10 +180,8 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde realRunnerClass = BlockJUnit4ClassRunner.class; } - onRunnerInitializing(); - try { - log("Initializing the inner runner: " + realRunnerClass); + Log.i(TAG, "Initializing the inner runner: " + realRunnerClass); mRealRunner = instantiateRealRunner(realRunnerClass, testClass); mDescription = mRealRunner.getDescription(); @@ -201,8 +193,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde } catch (Throwable th) { // If we throw in the constructor, Tradefed may not report it and just ignore the class, // so record it and throw it when the test actually started. - log("Fatal: Exception detected in constructor: " + th.getMessage() + "\n" - + Log.getStackTraceString(th)); + Log.e(TAG, "Fatal: Exception detected in constructor", th); mExceptionInConstructor = new RuntimeException("Exception detected in constructor", th); mDescription = Description.createTestDescription(testClass, "Constructor"); @@ -236,8 +227,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde if (!isOnRavenwood()) { return; } - - log("onRunnerInitializing"); + // DO NOT USE android.util.Log before calling onRunnerInitializing(). RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass); @@ -250,7 +240,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde if (!isOnRavenwood()) { return; } - log("runAnnotatedMethodsOnRavenwood() " + annotationClass.getName()); + Log.v(TAG, "runAnnotatedMethodsOnRavenwood() " + annotationClass.getName()); for (var method : getTestClass().getAnnotatedMethods(annotationClass)) { ensureIsPublicVoidMethod(method.getMethod(), /* isStatic=*/ instance == null); diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index 0f955e772445..c519204d0586 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -15,6 +15,9 @@ */ package com.android.platform.test.ravenwood.runtimehelper; +import android.system.ErrnoException; +import android.system.Os; + import com.android.ravenwood.common.RavenwoodCommonUtils; import java.io.File; @@ -37,6 +40,14 @@ public class ClassLoadHook { private static final boolean SKIP_LOADING_LIBANDROID = "1".equals(System.getenv( "RAVENWOOD_SKIP_LOADING_LIBANDROID")); + /** + * If set to 1, and if $ANDROID_LOG_TAGS isn't set, we enable the verbose logging. + * + * (See also InitLogging() in http://ac/system/libbase/logging.cpp) + */ + private static final boolean RAVENWOOD_VERBOSE_LOGGING = "1".equals(System.getenv( + "RAVENWOOD_VERBOSE")); + public static final String CORE_NATIVE_CLASSES = "core_native_classes"; public static final String ICU_DATA_PATH = "icu.data.path"; public static final String KEYBOARD_PATHS = "keyboard_paths"; @@ -123,6 +134,15 @@ public class ClassLoadHook { return; } + if (RAVENWOOD_VERBOSE_LOGGING) { + log("Force enabling verbose logging"); + try { + Os.setenv("ANDROID_LOG_TAGS", "*:v", true); + } catch (ErrnoException e) { + // Shouldn't happen. + } + } + // Make sure these properties are not set. ensurePropertyNotSet(CORE_NATIVE_CLASSES); ensurePropertyNotSet(ICU_DATA_PATH); diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java index a5c0b54a8637..c94ef31a5e5e 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java @@ -93,4 +93,8 @@ public final class Os { throw new ErrnoException("pread", OsConstants.EIO, e); } } + + public static void setenv(String name, String value, boolean overwrite) throws ErrnoException { + RavenwoodRuntimeNative.setenv(name, value, overwrite); + } } diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java index 0d8408c12033..ad80d92686ab 100644 --- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java @@ -53,6 +53,9 @@ public class RavenwoodRuntimeNative { private static native int nOpen(String path, int flags, int mode) throws ErrnoException; + public static native void setenv(String name, String value, boolean overwrite) + throws ErrnoException; + public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException { return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence); } diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp index f5cb019f4e7e..c255be5f61aa 100644 --- a/ravenwood/runtime-jni/ravenwood_runtime.cpp +++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp @@ -214,6 +214,19 @@ static jint Linux_open(JNIEnv* env, jobject, jstring javaPath, jint flags, jint return throwIfMinusOne(env, "open", TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode))); } +static void Linux_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaValue, + jboolean overwrite) { + ScopedRealUtf8Chars name(env, javaName); + if (name.c_str() == NULL) { + jniThrowNullPointerException(env); + } + ScopedRealUtf8Chars value(env, javaValue); + if (value.c_str() == NULL) { + jniThrowNullPointerException(env); + } + throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0)); +} + // ---- Registration ---- static const JNINativeMethod sMethods[] = @@ -227,6 +240,7 @@ static const JNINativeMethod sMethods[] = { "lstat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_lstat }, { "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat }, { "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open }, + { "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv }, }; extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) diff --git a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java index e56f81f0034a..eba628dc1fba 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/SyncAppSearchCallHelper.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java @@ -20,11 +20,16 @@ import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANA import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSession; import android.app.appsearch.GetSchemaResponse; +import android.app.appsearch.PutDocumentsRequest; +import android.app.appsearch.SearchResult; +import android.app.appsearch.SearchResults; +import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; import android.app.appsearch.SetSchemaResponse; import android.util.Slog; @@ -33,22 +38,20 @@ import com.android.internal.infra.AndroidFuture; import java.io.Closeable; import java.io.IOException; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; /** - * Helper class for interacting with a system server local appsearch session asynchronously. - * - * <p>Converts the AppSearch Callback API to {@link AndroidFuture}. + * A future API wrapper of {@link AppSearchSession} APIs. */ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) -public class SyncAppSearchCallHelper implements Closeable { - private static final String TAG = SyncAppSearchCallHelper.class.getSimpleName(); +public class FutureAppSearchSession implements Closeable { + private static final String TAG = FutureAppSearchSession.class.getSimpleName(); private final Executor mExecutor; - private final AppSearchManager mAppSearchManager; private final AndroidFuture<AppSearchResult<AppSearchSession>> mSettableSessionFuture; - public SyncAppSearchCallHelper( + public FutureAppSearchSession( @NonNull AppSearchManager appSearchManager, @NonNull Executor executor, @NonNull SearchContext appSearchContext) { @@ -57,22 +60,21 @@ public class SyncAppSearchCallHelper implements Closeable { Objects.requireNonNull(appSearchContext); mExecutor = executor; - mAppSearchManager = appSearchManager; mSettableSessionFuture = new AndroidFuture<>(); - mAppSearchManager.createSearchSession( + appSearchManager.createSearchSession( appSearchContext, mExecutor, mSettableSessionFuture::complete); } /** Converts a failed app search result codes into an exception. */ @NonNull - private static Exception failedResultToException(@NonNull AppSearchResult appSearchResult) { + private static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) { return switch (appSearchResult.getResultCode()) { - case AppSearchResult.RESULT_INVALID_ARGUMENT -> - new IllegalArgumentException(appSearchResult.getErrorMessage()); - case AppSearchResult.RESULT_IO_ERROR -> - new IOException(appSearchResult.getErrorMessage()); - case AppSearchResult.RESULT_SECURITY_ERROR -> - new SecurityException(appSearchResult.getErrorMessage()); + case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException( + appSearchResult.getErrorMessage()); + case AppSearchResult.RESULT_IO_ERROR -> new IOException( + appSearchResult.getErrorMessage()); + case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException( + appSearchResult.getErrorMessage()); default -> new IllegalStateException(appSearchResult.getErrorMessage()); }; } @@ -91,7 +93,7 @@ public class SyncAppSearchCallHelper implements Closeable { /** Gets the schema for a given app search session. */ public AndroidFuture<GetSchemaResponse> getSchema() { return getSessionAsync() - .thenComposeAsync( + .thenCompose( session -> { AndroidFuture<AppSearchResult<GetSchemaResponse>> settableSchemaResponse = new AndroidFuture<>(); @@ -105,14 +107,13 @@ public class SyncAppSearchCallHelper implements Closeable { failedResultToException(result)); } }); - }, - mExecutor); + }); } /** Sets the schema for a given app search session. */ public AndroidFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest setSchemaRequest) { return getSessionAsync() - .thenComposeAsync( + .thenCompose( session -> { AndroidFuture<AppSearchResult<SetSchemaResponse>> settableSchemaResponse = new AndroidFuture<>(); @@ -130,8 +131,32 @@ public class SyncAppSearchCallHelper implements Closeable { failedResultToException(result)); } }); - }, - mExecutor); + }); + } + + /** Indexes documents into the AppSearchSession database. */ + public AndroidFuture<AppSearchBatchResult<String, Void>> put( + @NonNull PutDocumentsRequest putDocumentsRequest) { + return getSessionAsync().thenCompose( + session -> { + AndroidFuture<AppSearchBatchResult<String, Void>> batchResultFuture = + new AndroidFuture<>(); + + session.put(putDocumentsRequest, mExecutor, batchResultFuture::complete); + return batchResultFuture; + }); + } + + /** + * Retrieves documents from the open AppSearchSession that match a given query string and type + * of search provided. + */ + public AndroidFuture<FutureSearchResults> search( + @NonNull String queryExpression, + @NonNull SearchSpec searchSpec) { + return getSessionAsync().thenApply( + session -> session.search(queryExpression, searchSpec)) + .thenApply(result -> new FutureSearchResults(result, mExecutor)); } @Override @@ -142,4 +167,32 @@ public class SyncAppSearchCallHelper implements Closeable { Slog.e(TAG, "Failed to close app search session", ex); } } + + /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */ + public static class FutureSearchResults { + private final SearchResults mSearchResults; + private final Executor mExecutor; + + public FutureSearchResults(@NonNull SearchResults searchResults, + @NonNull Executor executor) { + mSearchResults = Objects.requireNonNull(searchResults); + mExecutor = Objects.requireNonNull(executor); + } + + public AndroidFuture<List<SearchResult>> getNextPage() { + AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture = + new AndroidFuture<>(); + + mSearchResults.getNextPage(mExecutor, nextPageFuture::complete); + return nextPageFuture.thenApply(result -> { + if (result.isSuccess()) { + return result.getResultValue(); + } else { + throw new RuntimeException( + failedResultToException(result)); + } + }); + } + + } } diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d4f729cfbaf6..36665240c16b 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1516,9 +1516,8 @@ public final class ActiveServices { serviceName, FrameworkStatsLog.SERVICE_STATE_CHANGED__STATE__START); mAm.mBatteryStatsService.noteServiceStartRunning(uid, packageName, serviceName); final ProcessRecord hostApp = r.app; - final boolean wasStopped = hostApp == null ? wasStopped(r) : false; - final boolean firstLaunch = - hostApp == null ? !mAm.wasPackageEverLaunched(r.packageName, r.userId) : false; + final boolean wasStopped = hostApp == null ? r.appInfo.isStopped() : false; + final boolean firstLaunch = hostApp == null ? r.appInfo.isNotLaunched() : false; String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false /* whileRestarting */, @@ -4308,9 +4307,8 @@ public final class ActiveServices { true, UNKNOWN_ADJ); } - final boolean wasStopped = hostApp == null ? wasStopped(s) : false; - final boolean firstLaunch = - hostApp == null ? !mAm.wasPackageEverLaunched(s.packageName, s.userId) : false; + final boolean wasStopped = hostApp == null ? s.appInfo.isStopped() : false; + final boolean firstLaunch = hostApp == null ? s.appInfo.isNotLaunched() : false; boolean needOomAdj = false; if (c.hasFlag(Context.BIND_AUTO_CREATE)) { @@ -9350,8 +9348,4 @@ public final class ActiveServices { return mCachedDeviceProvisioningPackage != null && mCachedDeviceProvisioningPackage.equals(packageName); } - - private boolean wasStopped(ServiceRecord serviceRecord) { - return (serviceRecord.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0; - } } diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 1b00cec90bc4..6aadcdc74870 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -33,6 +33,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.icu.text.SimpleDateFormat; import android.os.Binder; +import android.os.Debug; import android.os.FileUtils; import android.os.Handler; import android.os.IBinder.DeathRecipient; @@ -495,6 +496,10 @@ public final class AppStartInfoTracker { private void addBaseFieldsFromProcessRecord(ApplicationStartInfo start, ProcessRecord app) { if (app == null) { + if (DEBUG) { + Slog.w(TAG, + "app is null in addBaseFieldsFromProcessRecord: " + Debug.getCallers(4)); + } return; } final int definingUid = app.getHostingRecord() != null diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 8fe33d18152c..9e4666cca140 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1005,13 +1005,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo; final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName(); - if ((info.flags & ApplicationInfo.FLAG_STOPPED) != 0) { - queue.setActiveWasStopped(true); - } - final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND; - final boolean firstLaunch = !mService.wasPackageEverLaunched(info.packageName, r.userId); - queue.setActiveFirstLaunch(firstLaunch); + queue.setActiveWasStopped(info.isStopped()); + queue.setActiveFirstLaunch(info.isNotLaunched()); + final int intentFlags = r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND; final HostingRecord hostingRecord = new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, component, r.intent.getAction(), r.getHostingRecordTriggerType()); final boolean isActivityCapable = (r.options != null diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 13145214c1c1..da408266bfac 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -554,13 +554,11 @@ public class ContentProviderHelper { callingProcessState, proc.mState.getCurProcState(), false, 0L); } else { - final boolean stopped = - (cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0; + final boolean stopped = cpr.appInfo.isStopped(); final int packageState = stopped ? PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED : PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL; - final boolean firstLaunch = !mService.wasPackageEverLaunched( - cpi.packageName, userId); + final boolean firstLaunch = cpr.appInfo.isNotLaunched(); checkTime(startTime, "getContentProviderImpl: before start process"); proc = mService.startProcessLocked( cpi.processName, cpr.appInfo, false, 0, diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index bb0c24b4f8c6..5b4e57350c40 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -3394,24 +3394,33 @@ public final class ProcessList { hostingRecord.getDefiningUid(), hostingRecord.getDefiningProcessName()); final ProcessStateRecord state = r.mState; - final boolean wasStopped = (info.flags & ApplicationInfo.FLAG_STOPPED) != 0; + final boolean wasStopped = info.isStopped(); // Check if we should mark the processrecord for first launch after force-stopping if (wasStopped) { + boolean wasEverLaunched; + if (android.app.Flags.useAppInfoNotLaunched()) { + wasEverLaunched = !info.isNotLaunched(); + } else { + wasEverLaunched = mService.getPackageManagerInternal() + .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId); + } // Check if the hosting record is for an activity or not. Since the stopped // state tracking is handled differently to avoid WM calling back into AM, // store the state in the correct record if (hostingRecord.isTypeActivity()) { - final boolean wasPackageEverLaunched = mService - .wasPackageEverLaunched(r.getApplicationInfo().packageName, r.userId); // If the package was launched in the past but is currently stopped, only then // should it be considered as force-stopped. - @WindowProcessController.StoppedState int stoppedState = wasPackageEverLaunched + @WindowProcessController.StoppedState int stoppedState = wasEverLaunched ? STOPPED_STATE_FORCE_STOPPED : STOPPED_STATE_FIRST_LAUNCH; r.getWindowProcessController().setStoppedState(stoppedState); } else { - r.setWasForceStopped(true); - // first launch is computed just before logging, for non-activity types + if (android.app.Flags.useAppInfoNotLaunched()) { + // If it was launched before, then it must be a force-stop + r.setWasForceStopped(wasEverLaunched); + } else { + r.setWasForceStopped(true); + } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4e24cf38fe73..780eda604436 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -285,7 +285,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -411,6 +410,9 @@ public class AudioService extends IAudioService.Stub /** The controller for the volume UI. */ private final VolumeController mVolumeController = new VolumeController(); + /** Used only for testing to enable/disable the long press timeout volume actions. */ + private final AtomicBoolean mVolumeControllerLongPressEnabled = new AtomicBoolean(true); + // sendMsg() flags /** If the msg is already queued, replace it with this one. */ private static final int SENDMSG_REPLACE = 0; @@ -12554,6 +12556,15 @@ public class AudioService extends IAudioService.Stub if (DEBUG_VOL) Log.d(TAG, "Volume controller visible: " + visible); } + /** @see AudioManager#setVolumeControllerLongPressTimeoutEnabled(boolean) */ + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setVolumeControllerLongPressTimeoutEnabled(boolean enable) { + super.setVolumeControllerLongPressTimeoutEnabled_enforcePermission(); + mVolumeControllerLongPressEnabled.set(enable); + Log.i(TAG, "Volume controller long press timeout enabled: " + enable); + } + @Override public void setVolumePolicy(VolumePolicy policy) { enforceVolumeController("set volume policy"); @@ -12632,7 +12643,9 @@ public class AudioService extends IAudioService.Stub if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) { // UI is not visible yet, adjustment is ignored if (mNextLongPress < now) { - mNextLongPress = now + mLongPressTimeout; + mNextLongPress = + now + (mVolumeControllerLongPressEnabled.get() ? mLongPressTimeout + : 0); } suppress = true; } else if (mNextLongPress > 0) { // in a long-press diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 907e7c639352..86015acc232f 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -938,7 +938,7 @@ public class AutomaticBrightnessController { setAmbientLux(mFastAmbientLux); if (mLoggingEnabled) { Slog.d(TAG, "updateAmbientLux: " - + ((mFastAmbientLux > mAmbientLux) ? "Brightened" : "Darkened") + ": " + + ((mFastAmbientLux > mPreThresholdLux) ? "Brightened" : "Darkened") + ": " + "mAmbientBrighteningThreshold=" + mAmbientBrighteningThreshold + ", " + "mAmbientDarkeningThreshold=" + mAmbientDarkeningThreshold + ", " + "mAmbientLightRingBuffer=" + mAmbientLightRingBuffer + ", " diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java index 334dda079e7a..01bbd2fb39f8 100644 --- a/services/core/java/com/android/server/display/DisplayBrightnessState.java +++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java @@ -17,6 +17,7 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; +import android.os.PowerManager; import android.text.TextUtils; import com.android.server.display.brightness.BrightnessEvent; @@ -255,7 +256,7 @@ public final class DisplayBrightnessState { private String mDisplayBrightnessStrategyName; private boolean mShouldUseAutoBrightness; private boolean mIsSlowChange; - private float mMaxBrightness; + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; private float mMinBrightness; private float mCustomAnimationRate = CUSTOM_ANIMATION_RATE_NOT_SET; private boolean mShouldUpdateScreenBrightnessSetting; diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 2b732eab67cc..ed16b1472ee5 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -134,6 +134,7 @@ import android.util.ArraySet; import android.util.EventLog; import android.util.IndentingPrintWriter; import android.util.IntArray; +import android.util.MathUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -1275,6 +1276,9 @@ public final class DisplayManagerService extends SystemService { || isUidPresentOnDisplayInternal(callingUid, displayId)) { return info; } + } else if (displayId == Display.DEFAULT_DISPLAY) { + Slog.e(TAG, "Default display is null for info request from uid " + + callingUid); } return null; } @@ -2223,10 +2227,11 @@ public final class DisplayManagerService extends SystemService { if (display.isValidLocked()) { applyDisplayChangedLocked(display); } - return; + } else { + releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); } - releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); + Slog.i(TAG, "Logical display removed: " + display.getDisplayIdLocked()); } private void releaseDisplayAndEmitEvent(LogicalDisplay display, int event) { @@ -3098,6 +3103,7 @@ public final class DisplayManagerService extends SystemService { /** * Get internal or external viewport. Create it if does not currently exist. + * * @param viewportType - either INTERNAL or EXTERNAL * @return the viewport with the requested type */ @@ -4413,7 +4419,6 @@ public final class DisplayManagerService extends SystemService { } - @Override // Binder call public BrightnessConfiguration getBrightnessConfigurationForUser(int userId) { final String uniqueId; @@ -4492,10 +4497,12 @@ public final class DisplayManagerService extends SystemService { @Override // Binder call public void setBrightness(int displayId, float brightness) { setBrightness_enforcePermission(); - if (!isValidBrightness(brightness)) { - Slog.w(TAG, "Attempted to set invalid brightness" + brightness); + if (Float.isNaN(brightness)) { + Slog.w(TAG, "Attempted to set invalid brightness: " + brightness); return; } + MathUtils.constrain(brightness, PowerManager.BRIGHTNESS_MIN, + PowerManager.BRIGHTNESS_MAX); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -4791,12 +4798,6 @@ public final class DisplayManagerService extends SystemService { } } - private static boolean isValidBrightness(float brightness) { - return !Float.isNaN(brightness) - && (brightness >= PowerManager.BRIGHTNESS_MIN) - && (brightness <= PowerManager.BRIGHTNESS_MAX); - } - @VisibleForTesting void overrideSensorManager(SensorManager sensorManager) { synchronized (mSyncRoot) { diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index bb2bed7281f7..7c591e3a2c03 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2222,6 +2222,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call unblockScreenOn(); } mWindowManagerPolicy.screenTurningOn(mDisplayId, mPendingScreenOnUnblocker); + Slog.i(TAG, "Window Manager Policy screenTurningOn complete"); } // Return true if the screen isn't blocked. diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java index 40e9198ea921..cf44ac029c82 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -73,7 +73,6 @@ abstract class BrightnessClamper<T> { abstract void stop(); protected enum Type { - THERMAL, POWER, WEAR_BEDTIME_MODE, } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index d3be33f51e5c..9404034cdd34 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -72,6 +72,8 @@ public class BrightnessClamperController { private final List<DisplayDeviceDataListener> mDisplayDeviceDataListeners = new ArrayList<>(); private final List<StatefulModifier> mStatefulModifiers = new ArrayList<>(); private final List<UserSwitchListener> mUserSwitchListeners = new ArrayList<>(); + private final List<DeviceConfigListener> mDeviceConfigListeners = new ArrayList<>(); + private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState(); private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener; @@ -144,9 +146,14 @@ public class BrightnessClamperController { if (m instanceof UserSwitchListener l) { mUserSwitchListeners.add(l); } + if (m instanceof DeviceConfigListener l) { + mDeviceConfigListeners.add(l); + } }); - mOnPropertiesChangedListener = - properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); + mOnPropertiesChangedListener = properties -> { + mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); + mDeviceConfigListeners.forEach(DeviceConfigListener::onDeviceConfigChanged); + }; mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId()); start(); } @@ -209,8 +216,6 @@ public class BrightnessClamperController { private int getBrightnessMaxReason() { if (mClamperType == null) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; - } else if (mClamperType == Type.THERMAL) { - return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; } else if (mClamperType == Type.POWER) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC; } else if (mClamperType == Type.WEAR_BEDTIME_MODE) { @@ -225,7 +230,7 @@ public class BrightnessClamperController { * Called when the user switches. */ public void onUserSwitch() { - mUserSwitchListeners.forEach(listener -> listener.onSwitchUser()); + mUserSwitchListeners.forEach(UserSwitchListener::onSwitchUser); } /** @@ -294,11 +299,14 @@ public class BrightnessClamperController { state2.mMaxDesiredHdrRatio) || !BrightnessSynchronizer.floatEquals(state1.mMaxHdrBrightness, state2.mMaxHdrBrightness) - || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline; + || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline + || state1.mMaxBrightnessReason != state2.mMaxBrightnessReason + || !BrightnessSynchronizer.floatEquals(state1.mMaxBrightness, + state2.mMaxBrightness); } private void start() { - if (!mClampers.isEmpty()) { + if (!mClampers.isEmpty() || !mDeviceConfigListeners.isEmpty()) { mDeviceConfigParameterProvider.addOnPropertiesChangedListener( mExecutor, mOnPropertiesChangedListener); } @@ -333,8 +341,7 @@ public class BrightnessClamperController { ClamperChangeListener clamperChangeListener, DisplayDeviceData data, DisplayManagerFlags flags, Context context, float currentBrightness) { List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>(); - clampers.add( - new BrightnessThermalClamper(handler, clamperChangeListener, data)); + if (flags.isPowerThrottlingClamperEnabled()) { // Check if power-throttling config is present. PowerThrottlingConfigData configData = data.getPowerThrottlingConfigData(); @@ -354,6 +361,8 @@ public class BrightnessClamperController { Handler handler, ClamperChangeListener listener, DisplayDeviceData data) { List<BrightnessStateModifier> modifiers = new ArrayList<>(); + modifiers.add(new BrightnessThermalModifier(handler, listener, data)); + modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) { @@ -384,7 +393,7 @@ public class BrightnessClamperController { /** * Config Data for clampers/modifiers */ - public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData, + public static class DisplayDeviceData implements BrightnessThermalModifier.ThermalData, BrightnessPowerClamper.PowerData, BrightnessWearBedtimeModeClamper.WearBedtimeModeData { @NonNull @@ -498,13 +507,23 @@ public class BrightnessClamperController { } /** + * Modifier should implement this interface in order to receive device config updates + */ + interface DeviceConfigListener { + void onDeviceConfigChanged(); + } + + /** * StatefulModifiers contribute to AggregatedState, that is used to decide if brightness - * adjustement is needed + * adjustment is needed */ public static class ModifiersAggregatedState { float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO; float mMaxHdrBrightness = PowerManager.BRIGHTNESS_MAX; @Nullable Spline mSdrHdrRatioSpline = null; + @BrightnessInfo.BrightnessMaxReason + int mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalModifier.java index 449825831182..21ef309fb327 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalModifier.java @@ -22,6 +22,8 @@ import static com.android.server.display.brightness.clamper.BrightnessClamperCon import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.hardware.display.BrightnessInfo; +import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.IThermalEventListener; import android.os.IThermalService; @@ -33,8 +35,10 @@ import android.provider.DeviceConfigInterface; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.utils.DeviceConfigParsingUtils; @@ -43,12 +47,15 @@ import com.android.server.display.utils.SensorUtils; import java.io.PrintWriter; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Function; -class BrightnessThermalClamper extends - BrightnessClamper<BrightnessThermalClamper.ThermalData> { +class BrightnessThermalModifier implements BrightnessStateModifier, + BrightnessClamperController.DisplayDeviceDataListener, + BrightnessClamperController.StatefulModifier, + BrightnessClamperController.DeviceConfigListener { private static final String TAG = "BrightnessThermalClamper"; @NonNull @@ -58,6 +65,11 @@ class BrightnessThermalClamper extends // data from DeviceConfig, for all displays, for all dataSets // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData)) @NonNull + protected final Handler mHandler; + @NonNull + protected final BrightnessClamperController.ClamperChangeListener mChangeListener; + + @NonNull private Map<String, Map<String, ThermalBrightnessThrottlingData>> mThermalThrottlingDataOverride = Map.of(); // data from DisplayDeviceConfig, for particular display+dataSet @@ -73,6 +85,8 @@ class BrightnessThermalClamper extends private String mDataId = null; @Temperature.ThrottlingStatus private int mThrottlingStatus = Temperature.THROTTLING_NONE; + private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; + private boolean mApplied = false; private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> { try { @@ -88,63 +102,104 @@ class BrightnessThermalClamper extends mDataSetMapper = ThermalBrightnessThrottlingData::create; - BrightnessThermalClamper(Handler handler, ClamperChangeListener listener, - ThermalData thermalData) { - this(new Injector(), handler, listener, thermalData); + BrightnessThermalModifier(Handler handler, ClamperChangeListener listener, + BrightnessClamperController.DisplayDeviceData data) { + this(new Injector(), handler, listener, data); } @VisibleForTesting - BrightnessThermalClamper(Injector injector, Handler handler, - ClamperChangeListener listener, ThermalData thermalData) { - super(handler, listener); + BrightnessThermalModifier(Injector injector, @NonNull Handler handler, + @NonNull ClamperChangeListener listener, + @NonNull BrightnessClamperController.DisplayDeviceData data) { + mHandler = handler; + mChangeListener = listener; mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); mThermalStatusObserver = new ThermalStatusObserver(injector, handler); mHandler.post(() -> { - setDisplayData(thermalData); + setDisplayData(data); loadOverrideData(); }); + } + //region BrightnessStateModifier + @Override + public void apply(DisplayManagerInternal.DisplayPowerRequest request, + DisplayBrightnessState.Builder stateBuilder) { + if (stateBuilder.getMaxBrightness() > mBrightnessCap) { + stateBuilder.setMaxBrightness(mBrightnessCap); + stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap)); + stateBuilder.setBrightnessMaxReason(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL); + stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); + // set fast change only when modifier is activated. + // this will allow auto brightness to apply slow change even when modifier is active + if (!mApplied) { + stateBuilder.setIsSlowChange(false); + } + mApplied = true; + } else { + mApplied = false; + } + } + @Override + public void stop() { + mThermalStatusObserver.stopObserving(); } @Override - @NonNull - Type getType() { - return Type.THERMAL; + public void dump(PrintWriter writer) { + writer.println("BrightnessThermalClamper:"); + writer.println(" mThrottlingStatus: " + mThrottlingStatus); + writer.println(" mUniqueDisplayId: " + mUniqueDisplayId); + writer.println(" mDataId: " + mDataId); + writer.println(" mDataOverride: " + mThermalThrottlingDataOverride); + writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig); + writer.println(" mDataActive: " + mThermalThrottlingDataActive); + writer.println(" mBrightnessCap:" + mBrightnessCap); + writer.println(" mApplied:" + mApplied); + mThermalStatusObserver.dump(writer); } @Override - void onDeviceConfigChanged() { - mHandler.post(() -> { - loadOverrideData(); - recalculateActiveData(); - }); + public boolean shouldListenToLightSensor() { + return false; + } + + @Override + public void setAmbientLux(float lux) { + // noop } + //endregion + //region DisplayDeviceDataListener @Override - void onDisplayChanged(ThermalData data) { + public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) { mHandler.post(() -> { setDisplayData(data); recalculateActiveData(); }); } + //endregion + //region StatefulModifier @Override - void stop() { - mThermalStatusObserver.stopObserving(); + public void applyStateChange( + BrightnessClamperController.ModifiersAggregatedState aggregatedState) { + if (aggregatedState.mMaxBrightness > mBrightnessCap) { + aggregatedState.mMaxBrightness = mBrightnessCap; + aggregatedState.mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; + } } + //endregion + //region DeviceConfigListener @Override - void dump(PrintWriter writer) { - writer.println("BrightnessThermalClamper:"); - writer.println(" mThrottlingStatus: " + mThrottlingStatus); - writer.println(" mUniqueDisplayId: " + mUniqueDisplayId); - writer.println(" mDataId: " + mDataId); - writer.println(" mDataOverride: " + mThermalThrottlingDataOverride); - writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig); - writer.println(" mDataActive: " + mThermalThrottlingDataActive); - mThermalStatusObserver.dump(writer); - super.dump(writer); + public void onDeviceConfigChanged() { + mHandler.post(() -> { + loadOverrideData(); + recalculateActiveData(); + }); } + //endregion private void recalculateActiveData() { if (mUniqueDisplayId == null || mDataId == null) { @@ -176,14 +231,11 @@ class BrightnessThermalClamper extends private void recalculateBrightnessCap() { float brightnessCap = PowerManager.BRIGHTNESS_MAX; - boolean isActive = false; - if (mThermalThrottlingDataActive != null) { // Throttling levels are sorted by increasing severity for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) { if (level.thermalStatus <= mThrottlingStatus) { brightnessCap = level.brightness; - isActive = true; } else { // Throttling levels that are greater than the current status are irrelevant break; @@ -191,9 +243,8 @@ class BrightnessThermalClamper extends } } - if (brightnessCap != mBrightnessCap || mIsActive != isActive) { + if (brightnessCap != mBrightnessCap) { mBrightnessCap = brightnessCap; - mIsActive = isActive; mChangeListener.onChanged(); } } @@ -205,7 +256,6 @@ class BrightnessThermalClamper extends } } - private final class ThermalStatusObserver extends IThermalEventListener.Stub { private final Injector mInjector; private final Handler mHandler; @@ -228,7 +278,7 @@ class BrightnessThermalClamper extends String curType = mObserverTempSensor.type; mObserverTempSensor = tempSensor; - if (curType.equals(tempSensor.type)) { + if (Objects.equals(curType, tempSensor.type)) { Slog.d(TAG, "Thermal status observer already started"); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java index 234d6d323838..236333ee433d 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecFeatureAction.java @@ -53,6 +53,10 @@ abstract class HdmiCecFeatureAction { // Default state used in common by all the feature actions. protected static final int STATE_NONE = 0; + // Delay to query avr's audio status, some avrs could report the old volume first. It could + // show inverse mute state on TV. + protected static final long DELAY_GIVE_AUDIO_STATUS = 500; + // Internal state indicating the progress of action. protected int mState = STATE_NONE; diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 3c7b9d37e4c6..271836ac7d29 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -1638,6 +1638,10 @@ public class HdmiControlService extends SystemService { mHandler.post(new WorkSourceUidPreservingRunnable(runnable)); } + void runOnServiceThreadDelayed(Runnable runnable, long delay) { + mHandler.postDelayed(new WorkSourceUidPreservingRunnable(runnable), delay); + } + private void assertRunOnServiceThread() { if (Looper.myLooper() != mHandler.getLooper()) { throw new IllegalStateException("Should run on service thread."); diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index 7e18d8412aae..11682f664a15 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -180,15 +180,22 @@ final class SendKeyAction extends HdmiCecFeatureAction { && localDevice().getService().isAbsoluteVolumeBehaviorEnabled()) { sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), mTargetAddress), - __ -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus( - getSourceAddress(), - localDevice().findAudioReceiverAddress()))); + __ -> queryAvrAudioStatus()); } else { sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(getSourceAddress(), mTargetAddress)); } } + private void queryAvrAudioStatus() { + localDevice().mService.runOnServiceThreadDelayed( + () -> sendCommand(HdmiCecMessageBuilder.buildGiveAudioStatus( + getSourceAddress(), + localDevice().findAudioReceiverAddress())), + DELAY_GIVE_AUDIO_STATUS); + + } + @Override public boolean processCommand(HdmiCecMessage cmd) { // Send key action doesn't need any incoming CEC command, hence does not consume it. diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index af0ccf93c826..f7478799527c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4678,6 +4678,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + // TODO(b/356239178): Make dump proto multi-user aware. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { final int userId = mCurrentImeUserId; @@ -6103,17 +6104,40 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args, boolean isCritical) { - IInputMethodInvoker method; - ClientState client; - + final int argUserId = parseUserIdFromDumpArgs(args); final Printer p = new PrintWriterPrinter(pw); + p.println("Current Input Method Manager state:"); + p.println(" concurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled); + if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) { + mUserDataRepository.forAllUserData( + u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical)); + } else { + final int userId = argUserId != UserHandle.USER_NULL ? argUserId : mCurrentImeUserId; + final var userData = getUserData(userId); + dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical); + } + } + + @UserIdInt + private static int parseUserIdFromDumpArgs(String[] args) { + final int userIdx = Arrays.binarySearch(args, "--user"); + if (userIdx == -1 || userIdx == args.length - 1) { + return UserHandle.USER_NULL; + } + return Integer.parseInt(args[userIdx + 1]); + } + // TODO(b/356239178): Update dump format output to better group per-user info. + @BinderThread + private void dumpAsStringNoCheckForUser(UserData userData, FileDescriptor fd, PrintWriter pw, + String[] args, boolean isCritical) { + final Printer p = new PrintWriterPrinter(pw); + IInputMethodInvoker method; + ClientState client; + p.println(" UserId=" + userData.mUserId); synchronized (ImfLock.class) { - final int userId = mCurrentImeUserId; - final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - final var userData = getUserData(userId); - p.println("Current Input Method Manager state:"); - p.println(" concurrentMultiUserModeEnabled" + mConcurrentMultiUserModeEnabled); + final InputMethodSettings settings = InputMethodSettingsRepository.get( + userData.mUserId); final List<InputMethodInfo> methodList = settings.getMethodList(); int numImes = methodList.size(); p.println(" Input Methods:"); @@ -6133,7 +6157,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println(" sessionRequested=" + c.mSessionRequested); p.println(" sessionRequestedForAccessibility=" - + c.mSessionRequestedForAccessibility); + + c.mSessionRequestedForAccessibility); p.println(" curSession=" + c.mCurSession); p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId); p.println(" uid=" + c.mUid); @@ -6141,7 +6165,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. }; mClientController.forAllClients(clientControllerDump); final var bindingController = userData.mBindingController; - p.println(" mCurrentImeUserId=" + userData.mUserId); p.println(" mCurMethodId=" + bindingController.getSelectedMethodId()); client = userData.mCurClient; p.println(" mCurClient=" + client + " mCurSeq=" @@ -6234,8 +6257,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println("No input method client."); } synchronized (ImfLock.class) { - final int userId = mCurrentImeUserId; - final var userData = getUserData(userId); if (userData.mImeBindingState.mFocusedWindowClient != null && client != userData.mImeBindingState.mFocusedWindowClient) { p.println(" "); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 0cc50e68ea21..3523a3336a63 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -103,7 +103,7 @@ import java.util.Set; * - A remote interface definition (aidl) provided by the service used for communication. */ abstract public class ManagedServices { - protected final String TAG = getClass().getSimpleName(); + protected final String TAG = getClass().getSimpleName().replace('$', '.'); protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000; diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index 1c70af0a56ea..5ca5fda19b2f 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -397,6 +397,8 @@ public class PackageInfoUtils { ai.privateFlags |= flag(state.isInstantApp(), ApplicationInfo.PRIVATE_FLAG_INSTANT) | flag(state.isVirtualPreload(), ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD) | flag(state.isHidden(), ApplicationInfo.PRIVATE_FLAG_HIDDEN); + ai.privateFlagsExt |= + flag(state.isNotLaunched(), ApplicationInfo.PRIVATE_FLAG_EXT_NOT_LAUNCHED); if (state.getEnabledState() == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { ai.enabled = true; } else if (state.getEnabledState() diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 12e7fd010e3d..71cb8820761f 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -3668,7 +3668,7 @@ public final class PowerManagerService extends SystemService mBrightWhenDozingConfig); int wakefulness = powerGroup.getWakefulnessLocked(); if (DEBUG_SPEW) { - Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready + Slog.d(TAG, "updatePowerGroupsLocked: displayReady=" + ready + ", groupId=" + groupId + ", policy=" + policyToString(powerGroup.getPolicyLocked()) + ", mWakefulness=" diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 60b5e658dfc5..91a17a9e1c31 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -599,13 +599,6 @@ public final class TvInputManagerService extends SystemService { ComponentName component = it.next(); ServiceState serviceState = userState.serviceStateMap.get(component); if (serviceState != null && serviceState.sessionTokens.isEmpty()) { - if (serviceState.callback != null) { - try { - serviceState.service.unregisterCallback(serviceState.callback); - } catch (RemoteException e) { - Slog.e(TAG, "error in unregisterCallback", e); - } - } unbindService(serviceState); it.remove(); } @@ -667,13 +660,6 @@ public final class TvInputManagerService extends SystemService { // Unregister all callbacks and unbind all services. for (ServiceState serviceState : userState.serviceStateMap.values()) { if (serviceState.service != null) { - if (serviceState.callback != null) { - try { - serviceState.service.unregisterCallback(serviceState.callback); - } catch (RemoteException e) { - Slog.e(TAG, "error in unregisterCallback", e); - } - } unbindService(serviceState); } } @@ -3571,12 +3557,19 @@ public final class TvInputManagerService extends SystemService { @GuardedBy("mLock") private void unbindService(ServiceState serviceState) { - if (!serviceState.bound) { + if (serviceState == null || !serviceState.bound) { return; } if (DEBUG) { Slog.d(TAG, "unbindService(service=" + serviceState.component + ")"); } + if (serviceState.callback != null) { + try { + serviceState.service.unregisterCallback(serviceState.callback); + } catch (RemoteException e) { + Slog.e(TAG, "error in unregisterCallback", e); + } + } mContext.unbindService(serviceState.connection); serviceState.bound = false; serviceState.service = null; @@ -3794,9 +3787,9 @@ public final class TvInputManagerService extends SystemService { if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) { return; } - Slog.d("ServiceCallback", - "addHardwareInput: device id " + deviceId + ", " - + inputInfo.toString()); + Slog.d(TAG, "ServiceCallback: addHardwareInput, deviceId: " + deviceId + + ", inputInfo: " + inputInfo.toString() + " by " + mComponent + + ", userId: " + mUserId); mTvInputHardwareManager.addHardwareInput(deviceId, inputInfo); addHardwareInputLocked(inputInfo, mComponent, mUserId); } @@ -3815,6 +3808,9 @@ public final class TvInputManagerService extends SystemService { if (serviceState.hardwareInputMap.containsKey(inputInfo.getId())) { return; } + Slog.d(TAG, "ServiceCallback: addHdmiInput, id: " + id + + ", inputInfo: "+ inputInfo.toString() + " by " + mComponent + + ", userId: " + mUserId); mTvInputHardwareManager.addHdmiInput(id, inputInfo); addHardwareInputLocked(inputInfo, mComponent, mUserId); if (mOnScreenInputId != null && mOnScreenSessionState != null) { @@ -3845,8 +3841,8 @@ public final class TvInputManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - Slog.d("ServiceCallback", - "removeHardwareInput " + inputId + " by " + mComponent); + Slog.d(TAG, "ServiceCallback: removeHardwareInput, inputId: " + inputId + + " by " + mComponent + ", userId: " + mUserId); removeHardwareInputLocked(inputId, mUserId); } } finally { diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index ecd140e23ab6..96a25dac21e3 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -44,7 +44,6 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; -import com.android.internal.telephony.flags.Flags; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; @@ -324,11 +323,8 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { if (SubscriptionManager.isValidSubscriptionId(subId)) { // Get only configs as needed to save memory. final PersistableBundle carrierConfig = - Flags.fixCrashOnGettingConfigWhenPhoneIsGone() - ? CarrierConfigManager.getCarrierConfigSubset(mContext, subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS) - : mCarrierConfigManager.getConfigForSubId(subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + CarrierConfigManager.getCarrierConfigSubset(mContext, subId, + VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) { mReadySubIdsBySlotId.put(slotId, subId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 85e24c15c1a5..b6e45fc803f7 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -14799,7 +14799,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled) { + public void setSecondaryLockscreenEnabled(ComponentName who, boolean enabled, + PersistableBundle options) { Objects.requireNonNull(who, "ComponentName is null"); // Check can set secondary lockscreen enabled @@ -18644,11 +18645,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { toggleBackupServiceActive(caller.getUserId(), enabled); - if (Flags.backupServiceSecurityLogEventEnabled()) { - if (SecurityLog.isLoggingEnabled()) { - SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED, - caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0); - } + if (SecurityLog.isLoggingEnabled()) { + SecurityLog.writeEvent(SecurityLog.TAG_BACKUP_SERVICE_TOGGLED, + caller.getPackageName(), caller.getUserId(), enabled ? 1 : 0); } } diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp new file mode 100644 index 000000000000..b5cf98697d54 --- /dev/null +++ b/services/tests/appfunctions/Android.bp @@ -0,0 +1,59 @@ +// 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 { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "FrameworksAppFunctionsTests", + team: "trendy_team_machine_learning", + defaults: [ + "modules-utils-testable-device-config-defaults", + ], + + srcs: [ + "src/**/*.kt", + ], + + static_libs: [ + "androidx.test.core", + "androidx.test.runner", + "androidx.test.ext.truth", + "platform-test-annotations", + "services.appfunctions", + "servicestests-core-utils", + "truth", + "frameworks-base-testutils", + "androidx.test.rules", + ], + + libs: [ + "android.test.base", + "android.test.runner", + ], + + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], + + optimize: { + enabled: false, + }, +} diff --git a/services/tests/appfunctions/AndroidManifest.xml b/services/tests/appfunctions/AndroidManifest.xml new file mode 100644 index 000000000000..1d42b177db59 --- /dev/null +++ b/services/tests/appfunctions/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.appfunctionstests"> + + <application android:testOnly="true" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.frameworks.appfunctionstests" + android:label="Frameworks AppFunctions Services Tests" /> + +</manifest>
\ No newline at end of file diff --git a/services/tests/appfunctions/AndroidTest.xml b/services/tests/appfunctions/AndroidTest.xml new file mode 100644 index 000000000000..0650120afe24 --- /dev/null +++ b/services/tests/appfunctions/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs Frameworks AppFunctions Services Tests."> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="FrameworksAppFunctionsTests.apk" /> + </target_preparer> + + <option name="test-tag" value="FrameworksAppFunctionsTests" /> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.frameworks.appfunctionstests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration>
\ No newline at end of file diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt new file mode 100644 index 000000000000..d8ce39356d9c --- /dev/null +++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionStaticMetadataHelperTest.kt @@ -0,0 +1,57 @@ +/* + * 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.app.appfunctions + +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class AppFunctionStaticMetadataHelperTest { + + @Test + fun getStaticSchemaNameForPackage() { + val actualSchemaName = + AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage("com.example.app") + + assertThat(actualSchemaName).isEqualTo("AppFunctionStaticMetadata-com.example.app") + } + + @Test + fun getDocumentIdForAppFunction() { + val packageName = "com.example.app" + val functionId = "someFunction" + + val actualDocumentId = + AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction(packageName, functionId) + + assertThat(actualDocumentId).isEqualTo("com.example.app/someFunction") + } + + @Test + fun getStaticMetadataQualifiedId() { + val packageName = "com.example.app" + val functionId = "someFunction" + + val actualQualifiedId = + AppFunctionStaticMetadataHelper.getStaticMetadataQualifiedId(packageName, functionId) + + assertThat(actualQualifiedId) + .isEqualTo("android\$apps-db/app_functions#com.example.app/someFunction") + } +} diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt new file mode 100644 index 000000000000..5233f194d6c5 --- /dev/null +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.appfunctions + +import android.app.appfunctions.AppFunctionRuntimeMetadata +import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema +import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema +import android.app.appsearch.AppSearchManager +import android.app.appsearch.PutDocumentsRequest +import android.app.appsearch.SearchSpec +import android.app.appsearch.SetSchemaRequest +import androidx.test.platform.app.InstrumentationRegistry +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class FutureAppSearchSessionTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val appSearchManager = context.getSystemService(AppSearchManager::class.java) + private val testExecutor = MoreExecutors.directExecutor() + + @Before + @After + fun clearData() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { + val setSchemaRequest = SetSchemaRequest.Builder().build() + it.setSchema(setSchemaRequest) + } + } + + @Test + fun setSchema() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + ) + .build() + + val schema = session.setSchema(setSchemaRequest) + + assertThat(schema.get()).isNotNull() + } + } + + @Test + fun put() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + ) + .build() + val schema = session.setSchema(setSchemaRequest) + assertThat(schema.get()).isNotNull() + val appFunctionRuntimeMetadata = + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + val putDocumentsRequest: PutDocumentsRequest = + PutDocumentsRequest.Builder() + .addGenericDocuments(appFunctionRuntimeMetadata) + .build() + + val putResult = session.put(putDocumentsRequest) + + assertThat(putResult.get().isSuccess).isTrue() + } + } + + @Test + fun search() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + ) + .build() + val schema = session.setSchema(setSchemaRequest) + assertThat(schema.get()).isNotNull() + val appFunctionRuntimeMetadata = + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + val putDocumentsRequest: PutDocumentsRequest = + PutDocumentsRequest.Builder() + .addGenericDocuments(appFunctionRuntimeMetadata) + .build() + val putResult = session.put(putDocumentsRequest) + assertThat(putResult.get().isSuccess).isTrue() + + val searchResult = session.search("", SearchSpec.Builder().build()) + + val genericDocs = + searchResult.get().nextPage.get().stream().map { it.genericDocument }.toList() + assertThat(genericDocs).hasSize(1) + val foundAppFunctionRuntimeMetadata = AppFunctionRuntimeMetadata(genericDocs[0]) + assertThat(foundAppFunctionRuntimeMetadata.functionId).isEqualTo(TEST_FUNCTION_ID) + } + } + + private companion object { + const val TEST_DB: String = "test_db" + const val TEST_PACKAGE_NAME: String = "test_pkg" + const val TEST_FUNCTION_ID: String = "print" + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 5bb8ded6920e..52f1cbd89e13 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -107,6 +107,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.MessageQueue; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; @@ -3053,6 +3054,74 @@ public class DisplayManagerServiceTest { } @Test + public void testBrightnessUpdates() { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mShortMockedInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + final float invalidBrightness = -0.3f; + final float brightnessOff = -1.0f; + final float minimumBrightness = 0.0f; + final float validBrightness = 0.5f; + + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + + // set and check valid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(validBrightness, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // set and check invalid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, invalidBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(PowerManager.BRIGHTNESS_MIN, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // reset and check valid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(validBrightness, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // set and check brightness off + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, brightnessOff); + waitForIdleHandler(mPowerHandler); + assertEquals(PowerManager.BRIGHTNESS_MIN, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // reset and check valid brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, validBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(validBrightness, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + + // set and check minimum brightness + waitForIdleHandler(mPowerHandler); + displayManagerBinderService.setBrightness(Display.DEFAULT_DISPLAY, minimumBrightness); + waitForIdleHandler(mPowerHandler); + assertEquals(PowerManager.BRIGHTNESS_MIN, + displayManagerBinderService.getBrightness(Display.DEFAULT_DISPLAY), + FLOAT_TOLERANCE); + } + + @Test public void testResolutionChangeGetsBackedUp() throws Exception { when(mMockFlags.isResolutionBackupRestoreEnabled()).thenReturn(true); DisplayManagerService displayManager = diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index f9dc12258667..da79f301ee3c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -226,7 +226,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.6f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(false); mTestInjector.mCapturedChangeListener.onChanged(); @@ -250,7 +250,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.6f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); @@ -274,7 +274,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.8f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); @@ -298,7 +298,7 @@ public class BrightnessClamperControllerTest { float clampedBrightness = 0.6f; float customAnimationRate = 0.01f; when(mMockClamper.getBrightnessCap()).thenReturn(clampedBrightness); - when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.THERMAL); + when(mMockClamper.getType()).thenReturn(BrightnessClamper.Type.POWER); when(mMockClamper.getCustomAnimationRate()).thenReturn(customAnimationRate); when(mMockClamper.isActive()).thenReturn(true); mTestInjector.mCapturedChangeListener.onChanged(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalModifierTest.java index 9d16594fae93..35d384bccc7f 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalModifierTest.java @@ -18,13 +18,16 @@ package com.android.server.display.brightness.clamper; import static com.android.server.display.config.DisplayDeviceConfigTestUtilsKt.createSensorData; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerInternal; +import android.os.IBinder; import android.os.IThermalEventListener; import android.os.IThermalService; import android.os.PowerManager; @@ -33,12 +36,14 @@ import android.os.Temperature; import android.provider.DeviceConfig; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.android.internal.annotations.Keep; +import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.testutils.FakeDeviceConfigInterface; @@ -54,10 +59,13 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashMap; import java.util.List; +import java.util.Map; @RunWith(JUnitParamsRunner.class) -public class BrightnessThermalClamperTest { +public class BrightnessThermalModifierTest { + private static final int NO_MODIFIER = 0; private static final float FLOAT_TOLERANCE = 0.001f; @@ -66,28 +74,35 @@ public class BrightnessThermalClamperTest { private IThermalService mMockThermalService; @Mock private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; + @Mock + private DisplayManagerInternal.DisplayPowerRequest mMockRequest; + @Mock + private DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock + private IBinder mMockBinder; private final FakeDeviceConfigInterface mFakeDeviceConfigInterface = new FakeDeviceConfigInterface(); private final TestHandler mTestHandler = new TestHandler(null); - private BrightnessThermalClamper mClamper; + private BrightnessThermalModifier mModifier; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler, - mMockClamperChangeListener, new TestThermalData()); + when(mMockDisplayDeviceConfig.getTempSensor()) + .thenReturn(SensorData.loadTempSensorUnspecifiedConfig()); + mModifier = new BrightnessThermalModifier(new TestInjector(), mTestHandler, + mMockClamperChangeListener, + ClamperTestUtilsKt.createDisplayDeviceData(mMockDisplayDeviceConfig, mMockBinder)); mTestHandler.flush(); } - @Test - public void testTypeIsThermal() { - assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType()); - } @Test public void testNoThrottlingData() { - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + 0.3f, true, + PowerManager.BRIGHTNESS_MAX, 0.3f, + false, true); } @Keep @@ -129,15 +144,19 @@ public class BrightnessThermalClamperTest { @Temperature.ThrottlingStatus int throttlingStatus, boolean expectedActive, float expectedBrightness) throws RemoteException { IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); - mClamper.onDisplayChanged(new TestThermalData(throttlingLevels)); + onDisplayChange(throttlingLevels); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus)); mTestHandler.flush(); - assertEquals(expectedActive, mClamper.isActive()); - assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + expectedBrightness, expectedBrightness, + expectedActive, !expectedActive); } @Test @@ -148,13 +167,56 @@ public class BrightnessThermalClamperTest { IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); thermalEventListener.notifyThrottling(createSkinTemperature(throttlingStatus)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); - mClamper.onDisplayChanged(new TestThermalData(throttlingLevels)); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); + + onDisplayChange(throttlingLevels); + mTestHandler.flush(); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + expectedBrightness, expectedBrightness, + expectedActive, !expectedActive); + } + + @Test + public void testAppliesFastChangeOnlyOnActivation() throws RemoteException { + IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); + onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))); + mTestHandler.flush(); + + thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE)); + mTestHandler.flush(); + + // expectedSlowChange = false + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, false); + + // slowChange is unchanged + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, true); + } + + @Test + public void testCapsMaxBrightnessOnly_currentBrightnessIsLowAndFastChange() + throws RemoteException { + IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); + onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))); mTestHandler.flush(); - assertEquals(expectedActive, mClamper.isActive()); - assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE)); + mTestHandler.flush(); + + assertModifierState( + 0.1f, false, + 0.5f, 0.1f, + true, false); } @Test @@ -162,28 +224,37 @@ public class BrightnessThermalClamperTest { IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); thermalEventListener.notifyThrottling(createSkinTemperature(Temperature.THROTTLING_SEVERE)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); - mClamper.onDisplayChanged(new TestThermalData( - List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f)))); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); + + onDisplayChange(List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))); mTestHandler.flush(); - assertTrue(mClamper.isActive()); - assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, false); overrideThrottlingData("displayId,1,emergency,0.4"); - mClamper.onDeviceConfigChanged(); + mModifier.onDeviceConfigChanged(); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); overrideThrottlingData("displayId,1,moderate,0.4"); - mClamper.onDeviceConfigChanged(); + mModifier.onDeviceConfigChanged(); mTestHandler.flush(); - assertTrue(mClamper.isActive()); - assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.4f, 0.4f, + true, false); } @Test @@ -191,35 +262,41 @@ public class BrightnessThermalClamperTest { final int severity = PowerManager.THERMAL_STATUS_SEVERE; IThermalEventListener thermalEventListener = captureSkinThermalEventListener(); // Update config to listen to display type sensor. - final SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); - final TestThermalData thermalData = - new TestThermalData( - DISPLAY_ID, - DisplayDeviceConfig.DEFAULT_ID, - List.of(new ThrottlingLevel(severity, 0.5f)), - tempSensor); - mClamper.onDisplayChanged(thermalData); + SensorData tempSensor = createSensorData("DISPLAY", "VIRTUAL-SKIN-DISPLAY"); + + when(mMockDisplayDeviceConfig.getTempSensor()).thenReturn(tempSensor); + onDisplayChange(List.of(new ThrottlingLevel(severity, 0.5f))); mTestHandler.flush(); + verify(mMockThermalService).unregisterThermalEventListener(thermalEventListener); thermalEventListener = captureThermalEventListener(Temperature.TYPE_DISPLAY); - assertFalse(mClamper.isActive()); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); // Verify no throttling triggered when any other sensor notification received. thermalEventListener.notifyThrottling(createSkinTemperature(severity)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); thermalEventListener.notifyThrottling(createDisplayTemperature("OTHER-SENSOR", severity)); mTestHandler.flush(); - assertFalse(mClamper.isActive()); - - assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + PowerManager.BRIGHTNESS_MAX, PowerManager.BRIGHTNESS_MAX, + false, true); // Verify throttling triggered when display sensor of given name throttled. thermalEventListener.notifyThrottling(createDisplayTemperature(tempSensor.name, severity)); mTestHandler.flush(); - assertTrue(mClamper.isActive()); - assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + assertModifierState( + PowerManager.BRIGHTNESS_MAX, true, + 0.5f, 0.5f, + true, false); } private IThermalEventListener captureSkinThermalEventListener() throws RemoteException { @@ -248,65 +325,54 @@ public class BrightnessThermalClamperTest { DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data); } - private class TestInjector extends BrightnessThermalClamper.Injector { - @Override - IThermalService getThermalService() { - return mMockThermalService; - } - - @Override - DeviceConfigParameterProvider getDeviceConfigParameterProvider() { - return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface); - } + private void onDisplayChange(List<ThrottlingLevel> throttlingLevels) { + Map<String, ThermalBrightnessThrottlingData> throttlingLevelsMap = new HashMap<>(); + throttlingLevelsMap.put(DisplayDeviceConfig.DEFAULT_ID, + ThermalBrightnessThrottlingData.create(throttlingLevels)); + when(mMockDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId()) + .thenReturn(throttlingLevelsMap); + mModifier.onDisplayChanged(ClamperTestUtilsKt.createDisplayDeviceData( + mMockDisplayDeviceConfig, mMockBinder, DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID)); } - private static class TestThermalData implements BrightnessThermalClamper.ThermalData { - - private final String mUniqueDisplayId; - private final String mDataId; - private final ThermalBrightnessThrottlingData mData; - private final SensorData mTempSensor; - - private TestThermalData() { - this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null, - SensorData.loadTempSensorUnspecifiedConfig()); - } - - private TestThermalData(List<ThrottlingLevel> data) { - this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data, - SensorData.loadTempSensorUnspecifiedConfig()); - } - - private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data, - SensorData tempSensor) { - mUniqueDisplayId = uniqueDisplayId; - mDataId = dataId; - mData = ThermalBrightnessThrottlingData.create(data); - mTempSensor = tempSensor; - } - - @NonNull - @Override - public String getUniqueDisplayId() { - return mUniqueDisplayId; - } + private void assertModifierState( + float currentBrightness, + boolean currentSlowChange, + float maxBrightness, float brightness, + boolean isActive, + boolean isSlowChange) { + ModifiersAggregatedState modifierState = new ModifiersAggregatedState(); + DisplayBrightnessState.Builder stateBuilder = DisplayBrightnessState.builder(); + stateBuilder.setBrightness(currentBrightness); + stateBuilder.setIsSlowChange(currentSlowChange); + + int maxBrightnessReason = isActive ? BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL + : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; + int modifier = isActive ? BrightnessReason.MODIFIER_THROTTLED : NO_MODIFIER; + + mModifier.applyStateChange(modifierState); + assertThat(modifierState.mMaxBrightness).isEqualTo(maxBrightness); + assertThat(modifierState.mMaxBrightnessReason).isEqualTo(maxBrightnessReason); + + mModifier.apply(mMockRequest, stateBuilder); + + assertThat(stateBuilder.getMaxBrightness()).isWithin(FLOAT_TOLERANCE).of(maxBrightness); + assertThat(stateBuilder.getBrightness()).isWithin(FLOAT_TOLERANCE).of(brightness); + assertThat(stateBuilder.getBrightnessMaxReason()).isEqualTo(maxBrightnessReason); + assertThat(stateBuilder.getBrightnessReason().getModifier()).isEqualTo(modifier); + assertThat(stateBuilder.isSlowChange()).isEqualTo(isSlowChange); + } - @NonNull - @Override - public String getThermalThrottlingDataId() { - return mDataId; - } - @Nullable + private class TestInjector extends BrightnessThermalModifier.Injector { @Override - public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() { - return mData; + IThermalService getThermalService() { + return mMockThermalService; } - @NonNull @Override - public SensorData getTempSensor() { - return mTempSensor; + DeviceConfigParameterProvider getDeviceConfigParameterProvider() { + return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface); } } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt index 5fd848f6adcc..f21749e0b843 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt @@ -21,6 +21,7 @@ import android.view.Display import com.android.server.display.DisplayDeviceConfig import com.android.server.display.brightness.clamper.BrightnessClamperController.DisplayDeviceData +@JvmOverloads fun createDisplayDeviceData( displayDeviceConfig: DisplayDeviceConfig, displayToken: IBinder, diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index 6ace9f14757c..fca0cfbc7d2f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -20,6 +20,7 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_BOOT_UP; +import static com.android.server.hdmi.HdmiCecFeatureAction.DELAY_GIVE_AUDIO_STATUS; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; @@ -582,6 +583,9 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { ); mTestLooper.dispatchAll(); + mTestLooper.moveTimeForward(DELAY_GIVE_AUDIO_STATUS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains( HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(), getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java index 673140390ae7..ec44a918f8e8 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java @@ -17,6 +17,7 @@ package com.android.server.hdmi; import static com.android.server.hdmi.HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP; +import static com.android.server.hdmi.HdmiCecFeatureAction.DELAY_GIVE_AUDIO_STATUS; import static com.google.common.truth.Truth.assertThat; @@ -223,6 +224,9 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav ); mTestLooper.dispatchAll(); + mTestLooper.moveTimeForward(DELAY_GIVE_AUDIO_STATUS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains( HdmiCecMessageBuilder.buildUserControlPressed(getLogicalAddress(), getSystemAudioDeviceLogicalAddress(), CEC_KEYCODE_VOLUME_UP)); diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index f0850af5fc2e..51e0c33ff705 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1456,21 +1456,21 @@ public class SubscriptionManager { public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA; /** - * Bitmask for {@code SERVICE_CAPABILITY_VOICE}. + * Bitmask for {@link #SERVICE_CAPABILITY_VOICE}. * @hide */ public static final int SERVICE_CAPABILITY_VOICE_BITMASK = serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE); /** - * Bitmask for {@code SERVICE_CAPABILITY_SMS}. + * Bitmask for {@link #SERVICE_CAPABILITY_SMS}. * @hide */ public static final int SERVICE_CAPABILITY_SMS_BITMASK = serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS); /** - * Bitmask for {@code SERVICE_CAPABILITY_DATA}. + * Bitmask for {@link #SERVICE_CAPABILITY_DATA}. * @hide */ public static final int SERVICE_CAPABILITY_DATA_BITMASK = diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3ff1e2ca8dfb..3e226ccf2737 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -6907,7 +6907,6 @@ public class TelephonyManager { return false; } - // TODO(b/316183370): replace all @code with @link in javadoc after feature is released /** * @return true if the current device is "voice capable". * <p> @@ -6921,10 +6920,10 @@ public class TelephonyManager { * PackageManager.FEATURE_TELEPHONY system feature, which is available * on any device with a telephony radio, even if the device is * data-only. - * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice + * @deprecated Replaced by {@link #isDeviceVoiceCapable()}. Starting from Android 15, voice * capability may also be overridden by carriers for a given subscription. For voice capable - * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for - * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details. + * device (when {@link #isDeviceVoiceCapable} return {@code true}), caller should check for + * subscription-level voice capability as well. See {@link #isDeviceVoiceCapable} for details. */ @Deprecated public boolean isVoiceCapable() { @@ -6946,8 +6945,8 @@ public class TelephonyManager { * <p> * Starting from Android 15, voice capability may also be overridden by carrier for a given * subscription on a voice capable device. To check if a subscription is "voice capable", - * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if - * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included. + * call method {@link SubscriptionInfo#getServiceCapabilities()} and check if + * {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ @@ -6964,10 +6963,10 @@ public class TelephonyManager { * <p> * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are * disabled when device doesn't support sms. - * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS + * @deprecated Replaced by {@link #isDeviceSmsCapable()}. Starting from Android 15, SMS * capability may also be overridden by carriers for a given subscription. For SMS capable - * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for - * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details. + * device (when {@link #isDeviceSmsCapable} return {@code true}), caller should check for + * subscription-level SMS capability as well. See {@link #isDeviceSmsCapable} for details. */ @Deprecated public boolean isSmsCapable() { @@ -6986,8 +6985,8 @@ public class TelephonyManager { * <p> * Starting from Android 15, SMS capability may also be overridden by carriers for a given * subscription on an SMS capable device. To check if a subscription is "SMS capable", - * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if - * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included. + * call method {@link SubscriptionInfo#getServiceCapabilities()} and check if + * {@link SubscriptionManager#SERVICE_CAPABILITY_SMS} is included. * * @see SubscriptionInfo#getServiceCapabilities() */ diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index 27cc923a97db..189de6bdb44a 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -16,8 +16,6 @@ package com.android.internal.protolog; -import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -30,7 +28,6 @@ import static org.mockito.Mockito.when; import static java.io.File.createTempFile; -import android.content.Context; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.tools.ScenarioBuilder; @@ -45,6 +42,7 @@ import android.util.proto.ProtoInputStream; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogDataType; import com.android.internal.protolog.common.LogLevel; @@ -53,11 +51,11 @@ import com.google.common.truth.Truth; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; @@ -76,6 +74,7 @@ import java.util.concurrent.atomic.AtomicInteger; @RunWith(JUnit4.class) public class PerfettoProtoLogImplTest { private static final String TEST_PROTOLOG_DATASOURCE_NAME = "test.android.protolog"; + private static final String MOCK_VIEWER_CONFIG_FILE = "my/mock/viewer/config/file.pb"; private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation() .getTargetContext().getFilesDir(); @@ -92,29 +91,19 @@ public class PerfettoProtoLogImplTest { new TraceConfig(false, true, false) ); - private ProtoLogConfigurationService mProtoLogConfigurationService; - private PerfettoProtoLogImpl mProtoLog; - private Protolog.ProtoLogViewerConfig.Builder mViewerConfigBuilder; - private File mFile; - private Runnable mCacheUpdater; + private static ProtoLogConfigurationService sProtoLogConfigurationService; + private static PerfettoProtoLogImpl sProtoLog; + private static Protolog.ProtoLogViewerConfig.Builder sViewerConfigBuilder; + private static Runnable sCacheUpdater; - private ProtoLogViewerConfigReader mReader; + private static ProtoLogViewerConfigReader sReader; public PerfettoProtoLogImplTest() throws IOException { } - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - final Context testContext = getInstrumentation().getContext(); - mFile = testContext.getFileStreamPath("tracing_test.dat"); - //noinspection ResultOfMethodCallIgnored - mFile.delete(); - - TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false); - TestProtoLogGroup.TEST_GROUP.setLogToProto(false); - - mViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder() + @BeforeClass + public static void setUp() throws Exception { + sViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder() .addGroups( Protolog.ProtoLogViewerConfig.Group.newBuilder() .setId(1) @@ -160,33 +149,52 @@ public class PerfettoProtoLogImplTest { ViewerConfigInputStreamProvider viewerConfigInputStreamProvider = Mockito.mock( ViewerConfigInputStreamProvider.class); Mockito.when(viewerConfigInputStreamProvider.getInputStream()) - .thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray())); + .thenAnswer(it -> new ProtoInputStream(sViewerConfigBuilder.build().toByteArray())); - mCacheUpdater = () -> {}; - mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider)); + sCacheUpdater = () -> {}; + sReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider)); final ProtoLogDataSourceBuilder dataSourceBuilder = (onStart, onFlush, onStop) -> new ProtoLogDataSource( onStart, onFlush, onStop, TEST_PROTOLOG_DATASOURCE_NAME); - mProtoLogConfigurationService = - new ProtoLogConfigurationService(dataSourceBuilder); - mProtoLog = new PerfettoProtoLogImpl( - viewerConfigInputStreamProvider, mReader, () -> mCacheUpdater.run(), - TestProtoLogGroup.values(), dataSourceBuilder, mProtoLogConfigurationService); + final ViewerConfigFileTracer tracer = (dataSource, viewerConfigFilePath) -> { + Utils.dumpViewerConfig(dataSource, () -> { + if (!viewerConfigFilePath.equals(MOCK_VIEWER_CONFIG_FILE)) { + throw new RuntimeException( + "Unexpected viewer config file path provided"); + } + return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()); + }); + }; + sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer); + + if (android.tracing.Flags.clientSideProtoLogging()) { + sProtoLog = new PerfettoProtoLogImpl( + MOCK_VIEWER_CONFIG_FILE, sReader, () -> sCacheUpdater.run(), + TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); + } else { + sProtoLog = new PerfettoProtoLogImpl( + viewerConfigInputStreamProvider, sReader, () -> sCacheUpdater.run(), + TestProtoLogGroup.values(), dataSourceBuilder, sProtoLogConfigurationService); + } + } + + @Before + public void before() { + Mockito.reset(sReader); + + TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false); + TestProtoLogGroup.TEST_GROUP.setLogToProto(false); } @After public void tearDown() { - if (mFile != null) { - //noinspection ResultOfMethodCallIgnored - mFile.delete(); - } ProtoLogImpl.setSingleInstance(null); } @Test public void isEnabled_returnsFalseByDefault() { - assertFalse(mProtoLog.isProtoEnabled()); + assertFalse(sProtoLog.isProtoEnabled()); } @Test @@ -196,7 +204,7 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); } finally { traceMonitor.stop(mWriter); } @@ -209,12 +217,12 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); } finally { traceMonitor.stop(mWriter); } - assertFalse(mProtoLog.isProtoEnabled()); + assertFalse(sProtoLog.isProtoEnabled()); } @Test @@ -226,15 +234,15 @@ public class PerfettoProtoLogImplTest { traceMonitor.start(); // Shouldn't be logging anything except WTF unless explicitly requested in the group // override. - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -258,15 +266,15 @@ public class PerfettoProtoLogImplTest { ).build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -294,15 +302,15 @@ public class PerfettoProtoLogImplTest { ).build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -324,15 +332,15 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, + sProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4, LogDataType.BOOLEAN, new Object[]{true}); - mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, + sProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -351,8 +359,8 @@ public class PerfettoProtoLogImplTest { @Test public void log_logcatEnabled() { - when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f"); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f"); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); @@ -363,13 +371,13 @@ public class PerfettoProtoLogImplTest { verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( LogLevel.INFO), eq("test true 10000 % 0x7530 test 3.0E-6")); - verify(mReader).getViewerString(eq(1234L)); + verify(sReader).getViewerString(eq(1234L)); } @Test public void log_logcatEnabledInvalidMessage() { - when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f"); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn("test %b %d %% %x %s %f"); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); @@ -381,29 +389,32 @@ public class PerfettoProtoLogImplTest { LogLevel.INFO), eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", " + "args=(true, 10000, 1.0E-4, 2.0E-5, test)")); - verify(mReader).getViewerString(eq(1234L)); + verify(sReader).getViewerString(eq(1234L)); } @Test public void log_logcatEnabledNoMessage() { - when(mReader.getViewerString(anyLong())).thenReturn(null); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn(null); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); TestProtoLogGroup.TEST_GROUP.setLogToProto(false); - implSpy.log( + var assertion = assertThrows(RuntimeException.class, () -> implSpy.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, - new Object[]{5}); + new Object[]{5})); - verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( - LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)")); - verify(mReader).getViewerString(eq(1234L)); + verify(implSpy, never()).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( + LogLevel.INFO), any()); + verify(sReader).getViewerString(eq(1234L)); + + Truth.assertThat(assertion).hasMessageThat() + .contains("Failed to get log message with hash 1234 and args (5)"); } @Test public void log_logcatDisabled() { - when(mReader.getViewerString(anyLong())).thenReturn("test %d"); - PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog); + when(sReader.getViewerString(anyLong())).thenReturn("test %d"); + PerfettoProtoLogImpl implSpy = Mockito.spy(sProtoLog); TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false); implSpy.log( @@ -411,7 +422,7 @@ public class PerfettoProtoLogImplTest { new Object[]{5}); verify(implSpy, never()).passToLogcat(any(), any(), any()); - verify(mReader, never()).getViewerString(anyLong()); + verify(sReader, never()).getViewerString(anyLong()); } @Test @@ -426,11 +437,12 @@ public class PerfettoProtoLogImplTest { long before; long after; try { + assertFalse(sProtoLog.isProtoEnabled()); traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); before = SystemClock.elapsedRealtimeNanos(); - mProtoLog.log( + sProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash, 0b1110101001010100, new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true}); @@ -448,7 +460,8 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos()) .isAtMost(after); Truth.assertThat(protolog.messages.getFirst().getMessage()) - .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, 5.000000e-01, 0.6, true"); + .isEqualTo( + "My test message :: test, 1, 2, 3, 0.400000, 5.000000e-01, 0.6, true"); } @Test @@ -460,10 +473,10 @@ public class PerfettoProtoLogImplTest { long after; try { traceMonitor.start(); - assertTrue(mProtoLog.isProtoEnabled()); + assertTrue(sProtoLog.isProtoEnabled()); before = SystemClock.elapsedRealtimeNanos(); - mProtoLog.log( + sProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, "My test message :: %s, %d, %x, %f, %b", "test", 1, 3, 0.4, true); @@ -481,7 +494,7 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos()) .isAtMost(after); Truth.assertThat(protolog.messages.getFirst().getMessage()) - .isEqualTo("My test message :: test, 2, 6, 0.400000, true"); + .isEqualTo("My test message :: test, 1, 3, 0.400000, true"); } @Test @@ -491,7 +504,7 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -507,7 +520,7 @@ public class PerfettoProtoLogImplTest { private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) { final long messageId = new Random().nextLong(); - mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder() + sViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder() .setMessageId(messageId) .setMessage(message) .setLevel(logLevel) @@ -530,7 +543,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); before = SystemClock.elapsedRealtimeNanos(); - mProtoLog.log( + sProtoLog.log( LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash, 0b01100100, new Object[]{"test", 1, 0.1, true}); @@ -550,7 +563,7 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, 0b11, new Object[]{true}); } finally { traceMonitor.stop(mWriter); @@ -575,7 +588,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); - ProtoLogImpl.setSingleInstance(mProtoLog); + ProtoLogImpl.setSingleInstance(sProtoLog); ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1, 0b11, true); } finally { @@ -599,7 +612,7 @@ public class PerfettoProtoLogImplTest { @Test public void cacheIsUpdatedWhenTracesStartAndStop() { final AtomicInteger cacheUpdateCallCount = new AtomicInteger(0); - mCacheUpdater = cacheUpdateCallCount::incrementAndGet; + sCacheUpdater = cacheUpdateCallCount::incrementAndGet; PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder() .enableProtoLog(true, @@ -641,17 +654,17 @@ public class PerfettoProtoLogImplTest { @Test public void isEnabledUpdatesBasedOnRunningTraces() { - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse(); + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse(); PerfettoTraceMonitor traceMonitor1 = PerfettoTraceMonitor.newBuilder().enableProtoLog(true, @@ -670,65 +683,65 @@ public class PerfettoProtoLogImplTest { try { traceMonitor1.start(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isTrue(); try { traceMonitor2.start(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)).isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isTrue(); } finally { traceMonitor2.stop(mWriter); } - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isTrue(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isTrue(); } finally { traceMonitor1.stop(mWriter); } - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.DEBUG)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.VERBOSE)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.INFO)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WARN)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR)) .isFalse(); - Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) + Truth.assertThat(sProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)) .isFalse(); } @@ -741,7 +754,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, "My test null string: %s", (Object) null); } finally { traceMonitor.stop(mWriter); @@ -764,7 +777,7 @@ public class PerfettoProtoLogImplTest { try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, "My null args: %d, %f, %b", null, null, null); } finally { traceMonitor.stop(mWriter); @@ -775,7 +788,7 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages).hasSize(1); Truth.assertThat(protolog.messages.get(0).getMessage()) - .isEqualTo("My null args: 0, 0, false"); + .isEqualTo("My null args: 0, 0.000000, false"); } @Test @@ -798,7 +811,7 @@ public class PerfettoProtoLogImplTest { traceMonitor1.start(); traceMonitor2.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1, LogDataType.BOOLEAN, new Object[]{true}); } finally { traceMonitor1.stop(mWriter); @@ -827,12 +840,12 @@ public class PerfettoProtoLogImplTest { .build(); try { traceMonitor.start(); - mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, - "This message should not be logged"); - mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, - "This message should logged %d", 123); - mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, - "This message should also be logged %d", 567); + sProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, + "This message should not be logged"); + sProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, + "This message should be logged %d", 123); + sProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, + "This message should also be logged %d", 567); } finally { traceMonitor.stop(mWriter); } @@ -845,7 +858,7 @@ public class PerfettoProtoLogImplTest { Truth.assertThat(protolog.messages.get(0).getLevel()) .isEqualTo(LogLevel.WARN); Truth.assertThat(protolog.messages.get(0).getMessage()) - .isEqualTo("This message should logged 123"); + .isEqualTo("This message should be logged 123"); Truth.assertThat(protolog.messages.get(1).getLevel()) .isEqualTo(LogLevel.ERROR); @@ -853,6 +866,19 @@ public class PerfettoProtoLogImplTest { .isEqualTo("This message should also be logged 567"); } + @Test + public void throwsOnLogToLogcatForProcessedMessageMissingLoadedDefinition() { + TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true); + var protolog = new PerfettoProtoLogImpl(TestProtoLogGroup.values()); + + var exception = assertThrows(RuntimeException.class, () -> { + protolog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 123, 0, new Object[0]); + }); + + Truth.assertThat(exception).hasMessageThat() + .contains("Failed to get log message with hash 123"); + } + private enum TestProtoLogGroup implements IProtoLogGroup { TEST_GROUP(true, true, false, "TEST_TAG"); diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 887630b03a8c..b5cc5536532c 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -59,7 +59,6 @@ import android.os.HandlerExecutor; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.test.TestLooper; -import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -73,10 +72,7 @@ import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.telephony.flags.Flags; - import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -133,8 +129,6 @@ public class TelephonySubscriptionTrackerTest { TEST_SUBID_TO_CARRIER_CONFIG_MAP = Collections.unmodifiableMap(subIdToCarrierConfigMap); } - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - @NonNull private final Context mContext; @NonNull private final TestLooper mTestLooper; @@ -193,7 +187,6 @@ public class TelephonySubscriptionTrackerTest { @Before public void setUp() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_FIX_CRASH_ON_GETTING_CONFIG_WHEN_PHONE_IS_GONE); doReturn(2).when(mTelephonyManager).getActiveModemCount(); mCallback = mock(TelephonySubscriptionTrackerCallback.class); diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt index 0115339a68b7..245e802df49b 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt @@ -53,7 +53,7 @@ class ViewerConfigProtoBuilder : ProtoLogTool.ProtologViewerConfigBuilder { .setMessageId(key) .setMessage(log.messageString) .setLevel( - ProtoLogLevel.forNumber(log.logLevel.ordinal + 1)) + ProtoLogLevel.forNumber(log.logLevel.id)) .setGroupId(groupId) .setLocation(log.position) ) |