diff --git a/AconfigFlags.bp b/AconfigFlags.bp index dd919cacc534e830c4ec2466edac68a27ddc2df6..a0f38d9a7bd9251498f43702cdcbd85a77efeb54 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -680,6 +680,11 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +cc_aconfig_library { + name: "com.android.media.flags.editing-aconfig-cc", + aconfig_declarations: "com.android.media.flags.editing-aconfig", +} + // MediaProjection aconfig_declarations { name: "com.android.media.flags.projection-aconfig", diff --git a/Android.bp b/Android.bp index 5b9f2cbf2d0de2c29978f69fa722de202cca01d9..8c0158549d2d451d2f9a124c440f7cae351a4ef2 100644 --- a/Android.bp +++ b/Android.bp @@ -99,6 +99,7 @@ filegroup { ":android.hardware.biometrics.common-V4-java-source", ":android.hardware.biometrics.fingerprint-V5-java-source", ":android.hardware.biometrics.fingerprint.virtualhal-java-source", + ":android.hardware.biometrics.face.virtualhal-java-source", ":android.hardware.biometrics.face-V4-java-source", ":android.hardware.gnss-V2-java-source", ":android.hardware.graphics.common-V3-java-source", diff --git a/PERFORMANCE_OWNERS b/PERFORMANCE_OWNERS index 02b0a1ec75e7a9cd7942c9dfb3d864e6b2ae9fb1..d5d752f7ff23ae5fffe025d7f7dd2b318bc4f230 100644 --- a/PERFORMANCE_OWNERS +++ b/PERFORMANCE_OWNERS @@ -7,3 +7,4 @@ shayba@google.com jdduke@google.com shombert@google.com kevinjeon@google.com +yforta@google.com diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java index 0f3b1c366fb08b1dddf848d6975ec470f28c090d..033da2df9bf6d66e2eff936b062aa52910e75273 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java @@ -4944,10 +4944,14 @@ public class AlarmManagerService extends SystemService { @Override public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action == null) { + return; + } final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); synchronized (mLock) { String pkgList[] = null; - switch (intent.getAction()) { + switch (action) { case Intent.ACTION_QUERY_PACKAGE_RESTART: pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES); for (String packageName : pkgList) { diff --git a/core/api/current.txt b/core/api/current.txt index d0e20031c74ddb14fd689d614bb9d92edba9fda4..3ffab902f18d7fea0f0545bcdcbccacebbf3cd1c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5333,6 +5333,7 @@ package android.app { method @NonNull public String getProcessName(); method public int getRealUid(); method public int getReason(); + method @FlaggedApi("android.app.app_start_info_component") public int getStartComponent(); method public int getStartType(); method public int getStartupState(); method @NonNull public java.util.Map getStartupTimestamps(); @@ -5347,6 +5348,11 @@ package android.app { field public static final int STARTUP_STATE_ERROR = 1; // 0x1 field public static final int STARTUP_STATE_FIRST_FRAME_DRAWN = 2; // 0x2 field public static final int STARTUP_STATE_STARTED = 0; // 0x0 + field @FlaggedApi("android.app.app_start_info_component") public static final int START_COMPONENT_ACTIVITY = 1; // 0x1 + field @FlaggedApi("android.app.app_start_info_component") public static final int START_COMPONENT_BROADCAST = 2; // 0x2 + field @FlaggedApi("android.app.app_start_info_component") public static final int START_COMPONENT_CONTENT_PROVIDER = 3; // 0x3 + field @FlaggedApi("android.app.app_start_info_component") public static final int START_COMPONENT_OTHER = 5; // 0x5 + field @FlaggedApi("android.app.app_start_info_component") public static final int START_COMPONENT_SERVICE = 4; // 0x4 field public static final int START_REASON_ALARM = 0; // 0x0 field public static final int START_REASON_BACKUP = 1; // 0x1 field public static final int START_REASON_BOOT_COMPLETE = 2; // 0x2 @@ -6495,6 +6501,7 @@ package android.app { field public static final int FLAG_NO_CLEAR = 32; // 0x20 field public static final int FLAG_ONGOING_EVENT = 2; // 0x2 field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8 + field @FlaggedApi("android.app.api_rich_ongoing") public static final int FLAG_PROMOTED_ONGOING = 262144; // 0x40000 field @Deprecated public static final int FLAG_SHOW_LIGHTS = 1; // 0x1 field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0 field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2 @@ -6847,6 +6854,47 @@ package android.app { method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri); } + @FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style { + ctor public Notification.ProgressStyle(); + method @NonNull public android.app.Notification.ProgressStyle addProgressSegment(@NonNull android.app.Notification.ProgressStyle.Segment); + method @NonNull public android.app.Notification.ProgressStyle addProgressStep(@NonNull android.app.Notification.ProgressStyle.Step); + method public int getProgress(); + method @Nullable public android.graphics.drawable.Icon getProgressEndIcon(); + method public int getProgressMax(); + method @NonNull public java.util.List getProgressSegments(); + method @Nullable public android.graphics.drawable.Icon getProgressStartIcon(); + method @NonNull public java.util.List getProgressSteps(); + method @Nullable public android.graphics.drawable.Icon getProgressTrackerIcon(); + method public boolean isProgressIndeterminate(); + method public boolean isStyledByProgress(); + method @NonNull public android.app.Notification.ProgressStyle setProgress(int); + method @NonNull public android.app.Notification.ProgressStyle setProgressEndIcon(@Nullable android.graphics.drawable.Icon); + method @NonNull public android.app.Notification.ProgressStyle setProgressIndeterminate(boolean); + method @NonNull public android.app.Notification.ProgressStyle setProgressSegments(@NonNull java.util.List); + method @NonNull public android.app.Notification.ProgressStyle setProgressStartIcon(@Nullable android.graphics.drawable.Icon); + method @NonNull public android.app.Notification.ProgressStyle setProgressSteps(@NonNull java.util.List); + method @NonNull public android.app.Notification.ProgressStyle setProgressTrackerIcon(@Nullable android.graphics.drawable.Icon); + method @NonNull public android.app.Notification.ProgressStyle setStyledByProgress(boolean); + } + + public static final class Notification.ProgressStyle.Segment { + ctor public Notification.ProgressStyle.Segment(int); + method @ColorInt public int getColor(); + method public int getLength(); + method public int getStableId(); + method @NonNull public android.app.Notification.ProgressStyle.Segment setColor(@ColorInt int); + method @NonNull public android.app.Notification.ProgressStyle.Segment setStableId(int); + } + + public static final class Notification.ProgressStyle.Step { + ctor public Notification.ProgressStyle.Step(int); + method @ColorInt public int getColor(); + method public int getPosition(); + method public int getStableId(); + method @NonNull public android.app.Notification.ProgressStyle.Step setColor(@ColorInt int); + method @NonNull public android.app.Notification.ProgressStyle.Step setStableId(int); + } + public abstract static class Notification.Style { ctor @Deprecated public Notification.Style(); method public android.app.Notification build(); @@ -7008,6 +7056,7 @@ package android.app { method public boolean areNotificationsEnabled(); method public boolean areNotificationsPaused(); method public boolean canNotifyAsPackage(@NonNull String); + method @FlaggedApi("android.app.api_rich_ongoing") public boolean canPostPromotedNotifications(); method public boolean canUseFullScreenIntent(); method public void cancel(int); method public void cancel(@Nullable String, int); @@ -8724,13 +8773,15 @@ package android.app.admin { package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { - method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); + method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); } @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); + method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer); + method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -36934,14 +36985,16 @@ package android.provider { @FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState { ctor public ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState(int, @Nullable android.accounts.Account); - method @Nullable public android.accounts.Account getCloudAccount(); + method @Nullable public android.accounts.Account getAccount(); method public int getState(); method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account); method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofLocal(); method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofNotSet(); + method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofSim(@NonNull android.accounts.Account); field public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; // 0x3 field public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; // 0x2 field public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; // 0x1 + field public static final int DEFAULT_ACCOUNT_STATE_SIM = 4; // 0x4 } public static final class ContactsContract.RawContacts.DisplayPhoto { @@ -46645,6 +46698,8 @@ package android.telephony.data { field public static final int TYPE_IMS = 64; // 0x40 field public static final int TYPE_MCX = 1024; // 0x400 field public static final int TYPE_MMS = 2; // 0x2 + field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PAID = 65536; // 0x10000 + field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PRIVATE = 131072; // 0x20000 field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000 field public static final int TYPE_SUPL = 4; // 0x4 field public static final int TYPE_VSIM = 4096; // 0x1000 diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cc0354c32de2421ebd056e59be5975787f32b12e..bc34f5bfe13fe8c412b4e325331a976a7f607891 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6822,6 +6822,27 @@ package android.hardware.soundtrigger { field @NonNull public static final android.os.Parcelable.Creator CREATOR; } + @FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable { + method public int describeContents(); + method public int getAudioCapabilities(); + method @NonNull public byte[] getData(); + method @NonNull public java.util.List getKeyphrases(); + method public boolean isAllowMultipleTriggers(); + method public boolean isCaptureRequested(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class SoundTrigger.RecognitionConfig.Builder { + ctor public SoundTrigger.RecognitionConfig.Builder(); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig build(); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@Nullable byte[]); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection); + } + public static class SoundTrigger.RecognitionEvent { method @Nullable public android.media.AudioFormat getCaptureFormat(); method public int getCaptureSession(); @@ -7772,10 +7793,16 @@ package android.media.soundtrigger { method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID); method public int getDetectionServiceOperationsTimeout(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getModelState(@NonNull java.util.UUID); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties(); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean isRecognitionActive(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int stopRecognition(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int unloadSoundModel(@NonNull java.util.UUID); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model); } @@ -13020,6 +13047,8 @@ package android.service.notification { method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); method public final void unsnoozeNotification(@NonNull String); field public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS"; + field @FlaggedApi("android.service.notification.notification_classification") public static final String ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS"; + field @FlaggedApi("android.service.notification.notification_classification") public static final String EXTRA_NOTIFICATION_KEY = "android.service.notification.extra.NOTIFICATION_KEY"; field public static final String FEEDBACK_RATING = "feedback.rating"; field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; field public static final int SOURCE_FROM_APP = 0; // 0x0 @@ -15859,6 +15888,8 @@ package android.telephony.data { field public static final String TYPE_IMS_STRING = "ims"; field public static final String TYPE_MCX_STRING = "mcx"; field public static final String TYPE_MMS_STRING = "mms"; + field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PAID_STRING = "oem_paid"; + field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PRIVATE_STRING = "oem_private"; field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs"; field public static final String TYPE_SUPL_STRING = "supl"; field public static final String TYPE_VSIM_STRING = "vsim"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 72a68f85b6b7dd7c4ede0db631a9bf3d7fd5c8f2..0a10920154b8a51e0e9844e811620202d895927b 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -398,6 +398,7 @@ package android.app { method public android.content.ComponentName getEffectsSuppressor(); method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String); method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean); + method @FlaggedApi("android.app.api_rich_ongoing") public void setCanPostPromotedNotifications(@NonNull String, int, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean); method @FlaggedApi("android.app.modes_api") public boolean updateAutomaticZenRule(@NonNull String, @NonNull android.app.AutomaticZenRule, boolean); @@ -1613,15 +1614,15 @@ package android.hardware.camera2 { public final class CameraManager { method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException; method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException; - method @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static int getRotationOverrideInternal(@Nullable android.content.Context, @Nullable android.content.pm.PackageManager, @Nullable String); + method @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static int getRotationOverrideInternal(@Nullable android.content.Context, @Nullable android.content.pm.PackageManager, @Nullable String); method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, boolean, @Nullable android.os.Handler, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; method public static boolean shouldOverrideToPortrait(@Nullable android.content.pm.PackageManager, @Nullable String); field public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait"; field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L - field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_NONE = 0; // 0x0 - field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = 1; // 0x1 - field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_ROTATION_ONLY = 2; // 0x2 + field @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static final int ROTATION_OVERRIDE_NONE = 0; // 0x0 + field @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = 1; // 0x1 + field @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static final int ROTATION_OVERRIDE_ROTATION_ONLY = 2; // 0x2 } public abstract static class CameraManager.AvailabilityCallback { @@ -1885,17 +1886,9 @@ package android.hardware.soundtrigger { ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int); } - public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable { - ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int); + @FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable { + ctor @Deprecated public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int); ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]); - method public int describeContents(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator CREATOR; - field public final boolean allowMultipleTriggers; - field public final int audioCapabilities; - field public final boolean captureRequested; - field @NonNull public final byte[] data; - field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases; } public static class SoundTrigger.RecognitionEvent { @@ -2216,7 +2209,7 @@ package android.media.soundtrigger { public class SoundTriggerInstrumentation.RecognitionSession { method public void clearRecognitionCallback(); method public int getAudioSession(); - method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig(); + method @FlaggedApi("android.media.soundtrigger.manager_api") @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig(); method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback); method public void triggerAbortRecognition(); method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List); @@ -2227,7 +2220,6 @@ package android.media.soundtrigger { method @NonNull public android.media.soundtrigger.SoundTriggerManager createManagerForModule(@NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties); method @NonNull public android.media.soundtrigger.SoundTriggerManager createManagerForTestModule(); method @NonNull public static java.util.List listModuleProperties(); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel); } public static class SoundTriggerManager.Model { diff --git a/core/java/Android.bp b/core/java/Android.bp index 92bca3cfbef244ac27e79910261ae3ebd85fe0d1..99046328b1e272f31d6a21aee245dcbf543dcfc1 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -21,14 +21,52 @@ filegroup { "**/*.aidl", ":framework-nfc-non-updatable-sources", ":messagequeue-gen", + ":ranging_stack_mock_initializer", ], // Exactly one MessageQueue.java will be added to srcs by messagequeue-gen exclude_srcs: [ "android/os/*MessageQueue/**/*.java", + "android/ranging/**/*.java", ], visibility: ["//frameworks/base"], } +//Mock to allow service registry for ranging stack. +//TODO(b/331206299): Remove this after RELEASE_RANGING_STACK is ramped up to next. +soong_config_module_type { + name: "ranging_stack_framework_mock_init", + module_type: "genrule", + config_namespace: "bootclasspath", + bool_variables: [ + "release_ranging_stack", + ], + properties: [ + "srcs", + "cmd", + "out", + ], +} + +// The actual RangingFrameworkInitializer is present in packages/modules/Uwb/ranging/framework. +// Mock RangingFrameworkInitializer does nothing and allows to successfully build +// SystemServiceRegistry after registering for system service in SystemServiceRegistry both with +// and without build flag RELEASE_RANGING_STACK enabled. +ranging_stack_framework_mock_init { + name: "ranging_stack_mock_initializer", + soong_config_variables: { + release_ranging_stack: { + cmd: "touch $(out)", + // Adding an empty file as out is mandatory. + out: ["android/ranging/empty_ranging_fw.txt"], + conditions_default: { + srcs: ["android/ranging/mock/RangingFrameworkInitializer.java"], + cmd: "mkdir -p android/ranging/; cp $(in) $(out);", + out: ["android/ranging/RangingFrameworkInitializer.java"], + }, + }, + }, +} + // Add selected MessageQueue.java implementation to srcs soong_config_module_type { name: "release_package_messagequeue_implementation_srcs", diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3bc3a93060de27d708bb684646d7e89407a3b5a0..5b556cc226405aa76fb8b745f26264d1447ab90e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -230,6 +230,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.ReferrerIntent; +import com.android.internal.os.ApplicationSharedMemory; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderInternal; import com.android.internal.os.DebugStore; @@ -1301,6 +1302,7 @@ public final class ActivityThread extends ClientTransactionHandler long[] disabledCompatChanges, long[] loggableCompatChanges, SharedMemory serializedSystemFontMap, + FileDescriptor applicationSharedMemoryFd, long startRequestedElapsedTime, long startRequestedUptime) { if (services != null) { @@ -1329,6 +1331,16 @@ public final class ActivityThread extends ClientTransactionHandler ServiceManager.initServiceCache(services); } + // This must be initialized as early as possible to ensure availability for any + // downstream callers. + if (com.android.internal.os.Flags.applicationSharedMemoryEnabled()) { + ApplicationSharedMemory instance = + ApplicationSharedMemory.fromFileDescriptor( + applicationSharedMemoryFd, /* mutable= */ false); + instance.closeFileDescriptor(); + ApplicationSharedMemory.setInstance(instance); + } + setCoreSettings(coreSettings); AppBindData data = new AppBindData(); diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index 1e9a79bca65aecb728d44907906821a93a0a1ab3..edcdb6cc58ea8cda5c2b04b687d208b36f842e98 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -102,10 +102,10 @@ public final class ApplicationStartInfo implements Parcelable { /** Process started due to boot complete. */ public static final int START_REASON_BOOT_COMPLETE = 2; - /** Process started due to broadcast received. */ + /** Process started due to broadcast received for any reason not explicitly listed. */ public static final int START_REASON_BROADCAST = 3; - /** Process started due to access of ContentProvider */ + /** Process started due to access of ContentProvider for any reason not explicitly listed. */ public static final int START_REASON_CONTENT_PROVIDER = 4; /** * Process started to run scheduled job. */ @@ -123,7 +123,7 @@ public final class ApplicationStartInfo implements Parcelable { /** Process started due to push message. */ public static final int START_REASON_PUSH = 9; - /** Process service started. */ + /** Process started due to Service started for any reason not explicitly listed.. */ public static final int START_REASON_SERVICE = 10; /** Process started due to Activity started for any reason not explicitly listed. */ @@ -209,6 +209,26 @@ public final class ApplicationStartInfo implements Parcelable { /** Clock monotonic timestamp of surfaceflinger composition complete. */ public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7; + /** Process was started for an activity component. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO_COMPONENT) + public static final int START_COMPONENT_ACTIVITY = 1; + + /** Process was started for a broadcast component. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO_COMPONENT) + public static final int START_COMPONENT_BROADCAST = 2; + + /** Process was started for a content provider component. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO_COMPONENT) + public static final int START_COMPONENT_CONTENT_PROVIDER = 3; + + /** Process was started for a service component. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO_COMPONENT) + public static final int START_COMPONENT_SERVICE = 4; + + /** Process was started not for one of the four standard components. */ + @FlaggedApi(Flags.FLAG_APP_START_INFO_COMPONENT) + public static final int START_COMPONENT_OTHER = 5; + /** * @see #getMonoticCreationTimeMs */ @@ -279,6 +299,11 @@ public final class ApplicationStartInfo implements Parcelable { */ private boolean mWasForceStopped; + /** + * @see #getStartComponent() + */ + private @StartComponent int mStartComponent; + /** * @hide * */ @@ -343,6 +368,21 @@ public final class ApplicationStartInfo implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface LaunchMode {} + /** + * @hide * + */ + @IntDef( + prefix = {"START_COMPONENT_"}, + value = { + START_COMPONENT_ACTIVITY, + START_COMPONENT_BROADCAST, + START_COMPONENT_CONTENT_PROVIDER, + START_COMPONENT_SERVICE, + START_COMPONENT_OTHER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StartComponent {} + /** * @see #getStartupState * @hide @@ -479,6 +519,14 @@ public final class ApplicationStartInfo implements Parcelable { mWasForceStopped = wasForceStopped; } + /** + * @see #getStartComponent() + * @hide + */ + public void setStartComponent(@StartComponent int startComponent) { + mStartComponent = startComponent; + } + /** * Current state of startup. * @@ -567,6 +615,11 @@ public final class ApplicationStartInfo implements Parcelable { /** * The reason code of what triggered the process's start. * + * Start reason provides granular reasoning on why the app is being started. Start reason should + * not be used for distinguishing between the component the app is being started for as some + * reasons may overlap with multiple components, see {@link #getStartComponent} for this + * functionality instead. + * *

Note: field will be set for any {@link #getStartupState} value.

*/ public @StartReason int getReason() { @@ -654,6 +707,22 @@ public final class ApplicationStartInfo implements Parcelable { return mWasForceStopped; } + /** + * The component type that was being started which triggered the start. + * + * Start component should be used to accurately distinguish between the 4 component types: + * activity, service, broadcast, and content provider. This can be useful for optimizing + * startup flow by enabling the caller to only load the necessary dependencies for a specific + * component type. For more granular information on why the app is being started, see + * {@link #getReason}. + * + *

Note: field will be set for any {@link #getStartupState} value.

+ */ + @FlaggedApi(Flags.FLAG_APP_START_INFO_COMPONENT) + public @StartComponent int getStartComponent() { + return mStartComponent; + } + @Override public int describeContents() { return 0; @@ -680,6 +749,8 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeParcelable(mStartIntent, flags); dest.writeInt(mLaunchMode); dest.writeBoolean(mWasForceStopped); + dest.writeLong(mMonoticCreationTimeMs); + dest.writeInt(mStartComponent); } /** @hide */ @@ -703,6 +774,7 @@ public final class ApplicationStartInfo implements Parcelable { mLaunchMode = other.mLaunchMode; mWasForceStopped = other.mWasForceStopped; mMonoticCreationTimeMs = other.mMonoticCreationTimeMs; + mStartComponent = other.mStartComponent; } private ApplicationStartInfo(@NonNull Parcel in) { @@ -726,6 +798,7 @@ public final class ApplicationStartInfo implements Parcelable { mLaunchMode = in.readInt(); mWasForceStopped = in.readBoolean(); mMonoticCreationTimeMs = in.readLong(); + mStartComponent = in.readInt(); } private static String intern(@Nullable String source) { @@ -805,6 +878,7 @@ public final class ApplicationStartInfo implements Parcelable { proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped); proto.write(ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS, mMonoticCreationTimeMs); + proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent); proto.end(token); } @@ -892,6 +966,9 @@ public final class ApplicationStartInfo implements Parcelable { mMonoticCreationTimeMs = proto.readLong( ApplicationStartInfoProto.MONOTONIC_CREATION_TIME_MS); break; + case (int) ApplicationStartInfoProto.START_COMPONENT: + mStartComponent = proto.readInt(ApplicationStartInfoProto.START_COMPONENT); + break; } } proto.end(token); @@ -918,8 +995,11 @@ public final class ApplicationStartInfo implements Parcelable { .append(" reason=").append(reasonToString(mReason)) .append(" startType=").append(startTypeToString(mStartType)) .append(" launchMode=").append(mLaunchMode) - .append(" wasForceStopped=").append(mWasForceStopped) - .append('\n'); + .append(" wasForceStopped=").append(mWasForceStopped); + if (Flags.appStartInfoComponent()) { + sb.append(" startComponent=").append(startComponentToString(mStartComponent)); + } + sb.append('\n'); if (mStartIntent != null) { sb.append(" intent=").append(mStartIntent.toString()) .append('\n'); @@ -963,6 +1043,18 @@ public final class ApplicationStartInfo implements Parcelable { }; } + @FlaggedApi(Flags.FLAG_APP_START_INFO_COMPONENT) + private static String startComponentToString(@StartComponent int startComponent) { + return switch (startComponent) { + case START_COMPONENT_ACTIVITY -> "ACTIVITY"; + case START_COMPONENT_BROADCAST -> "BROADCAST"; + case START_COMPONENT_CONTENT_PROVIDER -> "CONTENT PROVIDER"; + case START_COMPONENT_SERVICE -> "SERVICE"; + case START_COMPONENT_OTHER -> "OTHER"; + default -> ""; + }; + } + /** @hide */ @Override public boolean equals(@Nullable Object other) { @@ -971,18 +1063,19 @@ public final class ApplicationStartInfo implements Parcelable { } final ApplicationStartInfo o = (ApplicationStartInfo) other; return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid - && mDefiningUid == o.mDefiningUid && mReason == o.mReason - && mStartupState == o.mStartupState && mStartType == o.mStartType - && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName) - && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped - && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs; + && mDefiningUid == o.mDefiningUid && mReason == o.mReason + && mStartupState == o.mStartupState && mStartType == o.mStartType + && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName) + && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped + && mMonoticCreationTimeMs == o.mMonoticCreationTimeMs + && mStartComponent == o.mStartComponent; } @Override public int hashCode() { return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState, mStartType, mLaunchMode, mProcessName, mStartupTimestampsNs, - mMonoticCreationTimeMs); + mMonoticCreationTimeMs, mStartComponent); } private boolean timestampsEquals(@NonNull ApplicationStartInfo other) { diff --git a/core/java/android/app/BroadcastStickyCache.java b/core/java/android/app/BroadcastStickyCache.java new file mode 100644 index 0000000000000000000000000000000000000000..d6f061be3b00682a1c9f8f225a38ef3d34edf92f --- /dev/null +++ b/core/java/android/app/BroadcastStickyCache.java @@ -0,0 +1,229 @@ +/* + * 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; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbManager; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.net.TetheringManager; +import android.net.nsd.NsdManager; +import android.net.wifi.WifiManager; +import android.net.wifi.p2p.WifiP2pManager; +import android.os.SystemProperties; +import android.os.UpdateLock; +import android.telephony.TelephonyManager; +import android.util.ArrayMap; +import android.view.WindowManagerPolicyConstants; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; + +/** @hide */ +public class BroadcastStickyCache { + + private static final String[] CACHED_BROADCAST_ACTIONS = { + AudioManager.ACTION_HDMI_AUDIO_PLUG, + AudioManager.ACTION_HEADSET_PLUG, + AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED, + AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED, + AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION, + AudioManager.RINGER_MODE_CHANGED_ACTION, + ConnectivityManager.CONNECTIVITY_ACTION, + Intent.ACTION_BATTERY_CHANGED, + Intent.ACTION_DEVICE_STORAGE_FULL, + Intent.ACTION_DEVICE_STORAGE_LOW, + Intent.ACTION_SIM_STATE_CHANGED, + NsdManager.ACTION_NSD_STATE_CHANGED, + TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED, + TetheringManager.ACTION_TETHER_STATE_CHANGED, + UpdateLock.UPDATE_LOCK_CHANGED, + UsbManager.ACTION_USB_STATE, + WifiManager.ACTION_WIFI_SCAN_AVAILABILITY_CHANGED, + WifiManager.NETWORK_STATE_CHANGED_ACTION, + WifiManager.SUPPLICANT_STATE_CHANGED_ACTION, + WifiManager.WIFI_STATE_CHANGED_ACTION, + WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, + WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED, + "android.net.conn.INET_CONDITION_ACTION" // ConnectivityManager.INET_CONDITION_ACTION + }; + + @GuardedBy("sCachedStickyBroadcasts") + private static final ArrayList sCachedStickyBroadcasts = + new ArrayList<>(); + + @GuardedBy("sCachedPropertyHandles") + private static final ArrayMap sCachedPropertyHandles = + new ArrayMap<>(); + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static boolean useCache(@Nullable IntentFilter filter) { + if (!shouldCache(filter)) { + return false; + } + synchronized (sCachedStickyBroadcasts) { + final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter); + if (cachedStickyBroadcast == null) { + return false; + } + final long version = cachedStickyBroadcast.propertyHandle.getLong(-1 /* def */); + return version > 0 && cachedStickyBroadcast.version == version; + } + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public static void add(@Nullable IntentFilter filter, @Nullable Intent intent) { + if (!shouldCache(filter)) { + return; + } + synchronized (sCachedStickyBroadcasts) { + CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter); + if (cachedStickyBroadcast == null) { + final String key = getKey(filter.getAction(0)); + final SystemProperties.Handle handle = SystemProperties.find(key); + final long version = handle == null ? -1 : handle.getLong(-1 /* def */); + if (version == -1) { + return; + } + cachedStickyBroadcast = new CachedStickyBroadcast(filter, handle); + sCachedStickyBroadcasts.add(cachedStickyBroadcast); + cachedStickyBroadcast.intent = intent; + cachedStickyBroadcast.version = version; + } else { + cachedStickyBroadcast.intent = intent; + cachedStickyBroadcast.version = cachedStickyBroadcast.propertyHandle + .getLong(-1 /* def */); + } + } + } + + private static boolean shouldCache(@Nullable IntentFilter filter) { + if (!Flags.useStickyBcastCache()) { + return false; + } + if (filter == null || filter.safeCountActions() != 1) { + return false; + } + if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, filter.getAction(0))) { + return false; + } + return true; + } + + @VisibleForTesting + @NonNull + public static String getKey(@NonNull String action) { + return "cache_key.system_server.sticky_bcast." + action; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @Nullable + public static Intent getIntentUnchecked(@NonNull IntentFilter filter) { + synchronized (sCachedStickyBroadcasts) { + final CachedStickyBroadcast cachedStickyBroadcast = getValueUncheckedLocked(filter); + return cachedStickyBroadcast.intent; + } + } + + @GuardedBy("sCachedStickyBroadcasts") + @Nullable + private static CachedStickyBroadcast getValueUncheckedLocked(@NonNull IntentFilter filter) { + for (int i = sCachedStickyBroadcasts.size() - 1; i >= 0; --i) { + final CachedStickyBroadcast cachedStickyBroadcast = sCachedStickyBroadcasts.get(i); + if (IntentFilter.filterEquals(filter, cachedStickyBroadcast.filter)) { + return cachedStickyBroadcast; + } + } + return null; + } + + public static void incrementVersion(@NonNull String action) { + if (!shouldIncrementVersion(action)) { + return; + } + final String key = getKey(action); + synchronized (sCachedPropertyHandles) { + SystemProperties.Handle handle = sCachedPropertyHandles.get(key); + final long version; + if (handle == null) { + handle = SystemProperties.find(key); + if (handle != null) { + sCachedPropertyHandles.put(key, handle); + } + } + version = handle == null ? 0 : handle.getLong(0 /* def */); + SystemProperties.set(key, String.valueOf(version + 1)); + if (handle == null) { + sCachedPropertyHandles.put(key, SystemProperties.find(key)); + } + } + } + + public static void incrementVersionIfExists(@NonNull String action) { + if (!shouldIncrementVersion(action)) { + return; + } + final String key = getKey(action); + synchronized (sCachedPropertyHandles) { + final SystemProperties.Handle handle = sCachedPropertyHandles.get(key); + if (handle == null) { + return; + } + final long version = handle.getLong(0 /* def */); + SystemProperties.set(key, String.valueOf(version + 1)); + } + } + + private static boolean shouldIncrementVersion(@NonNull String action) { + if (!Flags.useStickyBcastCache()) { + return false; + } + if (!ArrayUtils.contains(CACHED_BROADCAST_ACTIONS, action)) { + return false; + } + return true; + } + + @VisibleForTesting + public static void clearForTest() { + synchronized (sCachedStickyBroadcasts) { + sCachedStickyBroadcasts.clear(); + } + synchronized (sCachedPropertyHandles) { + sCachedPropertyHandles.clear(); + } + } + + private static final class CachedStickyBroadcast { + @NonNull public final IntentFilter filter; + @Nullable public Intent intent; + @IntRange(from = 0) public long version; + @NonNull public final SystemProperties.Handle propertyHandle; + + CachedStickyBroadcast(@NonNull IntentFilter filter, + @NonNull SystemProperties.Handle propertyHandle) { + this.filter = filter; + this.propertyHandle = propertyHandle; + } + } +} diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 90fba2962a232d5be6ddfd6a1463e8c9c98bbb2d..ecef0db46bb28bc74c71859fdf180a8d95f9ea1f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1921,10 +1921,19 @@ class ContextImpl extends Context { } } try { - final Intent intent = ActivityManager.getService().registerReceiverWithFeature( - mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), - AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId, - flags); + final Intent intent; + if (receiver == null && BroadcastStickyCache.useCache(filter)) { + intent = BroadcastStickyCache.getIntentUnchecked(filter); + } else { + intent = ActivityManager.getService().registerReceiverWithFeature( + mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(), + AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, + userId, + flags); + if (receiver == null) { + BroadcastStickyCache.add(filter, intent); + } + } if (intent != null) { intent.setExtrasClassLoader(getClassLoader()); // TODO: determine at registration time if caller is diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index c6fa064a6b0fbdfd29ed30ed5adc92e3c7c58e6c..12deaec08f53932243ca9919f7fb8855bb4c0465 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -29,16 +29,24 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; -import android.os.Handler; import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * The GameManager allows system apps to modify and query the game mode of apps. + * + *

Note: After {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, some devices + * that do not support the GameManager features may not publish a GameManager instance. + * These device types include: + *

    + *
  • Wear devices ({@link PackageManager#FEATURE_WATCH}) + *
+ * + *

Therefore, you should always do a {@code null} check on the return value of + * {@link Context#getSystemService(Class)} and {@link Context#getSystemService(String)} when trying + * to obtain an instance of GameManager on the aforementioned device types. */ @SystemService(Context.GAME_SERVICE) public final class GameManager { @@ -46,7 +54,7 @@ public final class GameManager { private static final String TAG = "GameManager"; private final @Nullable Context mContext; - private final IGameManagerService mService; + private final @Nullable IGameManagerService mService; /** @hide */ @IntDef(flag = false, prefix = {"GAME_MODE_"}, value = { @@ -92,10 +100,9 @@ public final class GameManager { */ public static final int GAME_MODE_CUSTOM = 4; - GameManager(Context context, Handler handler) throws ServiceNotFoundException { + GameManager(Context context, @Nullable IGameManagerService service) { mContext = context; - mService = IGameManagerService.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.GAME_SERVICE)); + mService = service; } /** @@ -145,6 +152,7 @@ public final class GameManager { // we don't want a binder call each time to check on behalf of an app using CompatChange. @SuppressWarnings("AndroidFrameworkCompatChange") private @GameMode int getGameModeImpl(@NonNull String packageName, int targetSdkVersion) { + if (mService == null) return GAME_MODE_UNSUPPORTED; final int gameMode; try { gameMode = mService.getGameMode(packageName, @@ -176,6 +184,7 @@ public final class GameManager { @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @Nullable GameModeInfo getGameModeInfo(@NonNull String packageName) { + if (mService == null) return null; try { return mService.getGameModeInfo(packageName, mContext.getUserId()); } catch (RemoteException e) { @@ -196,6 +205,7 @@ public final class GameManager { @UserHandleAware @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String packageName, @GameMode int gameMode) { + if (mService == null) return; try { mService.setGameMode(packageName, gameMode, mContext.getUserId()); } catch (RemoteException e) { @@ -212,6 +222,7 @@ public final class GameManager { */ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int[] getAvailableGameModes(@NonNull String packageName) { + if (mService == null) return new int[0]; try { return mService.getAvailableGameModes(packageName, mContext.getUserId()); } catch (RemoteException e) { @@ -232,6 +243,7 @@ public final class GameManager { @TestApi @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public boolean isAngleEnabled(@NonNull String packageName) { + if (mService == null) return false; try { return mService.isAngleEnabled(packageName, mContext.getUserId()); } catch (RemoteException e) { @@ -246,6 +258,7 @@ public final class GameManager { */ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void notifyGraphicsEnvironmentSetup() { + if (mService == null) return; try { mService.notifyGraphicsEnvironmentSetup( mContext.getPackageName(), mContext.getUserId()); @@ -259,6 +272,7 @@ public final class GameManager { * @param gameState An object set to the current state. */ public void setGameState(@NonNull GameState gameState) { + if (mService == null) return; try { mService.setGameState(mContext.getPackageName(), gameState, mContext.getUserId()); } catch (RemoteException e) { @@ -275,6 +289,7 @@ public final class GameManager { */ @TestApi public void setGameServiceProvider(@Nullable String packageName) { + if (mService == null) return; try { mService.setGameServiceProvider(packageName); } catch (RemoteException e) { @@ -296,6 +311,7 @@ public final class GameManager { @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void updateCustomGameModeConfiguration(@NonNull String packageName, @NonNull GameModeConfiguration gameModeConfig) { + if (mService == null) return; try { mService.updateCustomGameModeConfiguration(packageName, gameModeConfig, mContext.getUserId()); diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 9f3829e3d46f9816dc90371bc1190bd47a20b2ba..06d01ecfcf06c0a2b4a63a7e4c3b0a53c074eda1 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -92,6 +92,7 @@ oneway interface IApplicationThread { in Bundle coreSettings, in String buildSerial, in AutofillOptions autofillOptions, in ContentCaptureOptions contentCaptureOptions, in long[] disabledCompatChanges, in long[] loggableCompatChanges, in SharedMemory serializedSystemFontMap, + in FileDescriptor applicationSharedMemoryFd, long startRequestedElapsedTime, long startRequestedUptime); void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs); void scheduleExit(); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index e2bee64cbb7b92a1f6847e08d2d5171459e89da2..8a54b5d4ec4f34d3a692170e281ef5939a4f7806 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -258,4 +258,7 @@ interface INotificationManager @EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"}) void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener); + void setCanBePromoted(String pkg, int uid, boolean promote, boolean fromUser); + boolean appCanBePromoted(String pkg, int uid); + boolean canBePromoted(String pkg); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 81d2c890ee31ed9ac66567ede6ac1cf504c23b7a..a39f216d033e161cace3fdf90fba19bf2428ddc8 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -121,7 +121,6 @@ import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -772,10 +771,43 @@ public class Notification implements Parcelable @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG) public static final int FLAG_SILENT = 1 << 17; //0x00020000 - private static final List> PLATFORM_STYLE_CLASSES = Arrays.asList( - BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, - DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, - MessagingStyle.class, CallStyle.class); + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set by the system if this notification is a promoted ongoing notification, either via a + * user setting or allowlist. + * + * Applications cannot set this flag directly, but the posting app and + * {@link android.service.notification.NotificationListenerService} can read it. + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final int FLAG_PROMOTED_ONGOING = 0x00040000; + + private static final Set> PLATFORM_STYLE_CLASSES = Set.of( + BigTextStyle.class, + BigPictureStyle.class, + InboxStyle.class, + MediaStyle.class, + DecoratedCustomViewStyle.class, + DecoratedMediaCustomViewStyle.class, + MessagingStyle.class, + CallStyle.class + ); + + private static boolean isPlatformStyle(Style style) { + if (style == null) { + return false; + } + + if (PLATFORM_STYLE_CLASSES.contains(style.getClass())) { + return true; + } + + if (Flags.apiRichOngoing()) { + return style.getClass() == ProgressStyle.class; + } + + return false; + } /** @hide */ @IntDef(flag = true, prefix = {"FLAG_"}, value = { @@ -1608,6 +1640,66 @@ public class Notification implements Parcelable */ public static final String EXTRA_COLORIZED = "android.colorized"; + /** + * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Segment} + * bundles provided by a + * {@link android.app.Notification.ProgressStyle} notification as supplied to + * {@link ProgressStyle#setProgressSegments} + * or {@link ProgressStyle#addProgressSegment(ProgressStyle.Segment)}. + * This extra is a parcelable array list of bundles. + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments"; + + /** + * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Step} + * bundles provided by a + * {@link android.app.Notification.ProgressStyle} notification as supplied to + * {@link ProgressStyle#setProgressSteps} + * or {@link ProgressStyle#addProgressStep(ProgressStyle.Step)}. + * This extra is a parcelable array list of bundles. + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_PROGRESS_STEPS = "android.progressSteps"; + + /** + * {@link #extras} key: whether the progress bar should be styled by its progress as + * supplied to {@link ProgressStyle#setStyledByProgress}. + * This extra is a boolean. + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_STYLED_BY_PROGRESS = "android.styledByProgress"; + + /** + * {@link #extras} key: this is an {@link Icon} of an image to be + * shown as progress bar progress tracker icon in {@link ProgressStyle}, supplied to + *{@link ProgressStyle#setProgressTrackerIcon(Icon)}. + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_PROGRESS_TRACKER_ICON = "android.progressTrackerIcon"; + + /** + * {@link #extras} key: this is an {@link Icon} of an image to be + * shown at the beginning of the progress bar in {@link ProgressStyle}, supplied to + *{@link ProgressStyle#setProgressStartIcon(Icon)}. + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_PROGRESS_START_ICON = "android.progressStartIcon"; + + /** + * {@link #extras} key: this is an {@link Icon} of an image to be + * shown at the end of the progress bar in {@link ProgressStyle}, supplied to + *{@link ProgressStyle#setProgressEndIcon(Icon)}. + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_PROGRESS_END_ICON = "android.progressEndIcon"; + /** * @hide */ @@ -3061,6 +3153,9 @@ public class Notification implements Parcelable if (Flags.apiRichOngoing()) { visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class)); + visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class)); + visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class)); + visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class)); } if (mBubbleMetadata != null) { @@ -3109,6 +3204,53 @@ public class Notification implements Parcelable } } + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean containsCustomViews() { + return contentView != null + || bigContentView != null + || headsUpContentView != null + || (publicVersion != null + && (publicVersion.contentView != null + || publicVersion.bigContentView != null + || publicVersion.headsUpContentView != null)); + } + + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean hasTitle() { + return extras != null + && (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE)) + || !TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE_BIG))); + } + + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean hasPromotableStyle() { + //TODO(b/367739672): Add progress style + return extras == null || !extras.containsKey(Notification.EXTRA_TEMPLATE) + || isStyle(Notification.BigPictureStyle.class) + || isStyle(Notification.BigTextStyle.class) + || isStyle(Notification.CallStyle.class); + } + + /** + * @hide + */ + @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING) + public boolean hasPromotableCharacteristics() { + return isColorized() + && hasTitle() + && !containsCustomViews() + && hasPromotableStyle(); + } + /** * Whether this notification was posted by a headless system app. * @@ -6572,7 +6714,7 @@ public class Notification implements Parcelable // Custom views which come from a platform style class are safe, and thus do not need to // be wrapped. Any subclass of those styles has the opportunity to make arbitrary // changes to the RemoteViews, and thus can't be trusted as a fully vetted view. - if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) { + if (fromStyle && isPlatformStyle(mStyle)) { return false; } return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S; @@ -7636,7 +7778,6 @@ public class Notification implements Parcelable if (mLargeIcon != null || largeIcon != null) { Resources resources = context.getResources(); - Class style = getNotificationStyle(); int maxSize = resources.getDimensionPixelSize(isLowRam ? R.dimen.notification_right_icon_size_low_ram : R.dimen.notification_right_icon_size); @@ -7914,6 +8055,12 @@ public class Notification implements Parcelable return innerClass; } } + + if (Flags.apiRichOngoing()) { + if (templateClass.equals(ProgressStyle.class.getName())) { + return ProgressStyle.class; + } + } return null; } @@ -8781,6 +8928,16 @@ public class Notification implements Parcelable } } + /** + * @hide + */ + public boolean displayCustomViewInline() { + // This is a lie; True is returned for conversations to make sure that the custom + // view is not used instead of the template, but it will not actually be included. + return Flags.notificationNoCustomViewConversations() + && mConversationType != CONVERSATION_TYPE_LEGACY; + } + /** * @return the text that should be displayed in the statusBar when heads upped. * If {@code null} is returned, the default implementation will be used. @@ -11153,6 +11310,724 @@ public class Notification implements Parcelable } } + + /** + * A Notification Style used to to define a notification whose expanded state includes + * a highly customizable progress bar with segments, steps, a custom tracker icon, + * and custom icons at the start and end of the progress bar. + * + * This style is suggested for use cases where the app is showing a tracker to the + * user of a thing they are interested in: the location of a car on its way + * to pick them up, food being delivered, or their own progress in a navigation + * journey. + * + * To use this style with your Notification, feed it to + * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so: + *

+     * new Notification.Builder(context)
+     *   .setSmallIcon(R.drawable.ic_notification)
+     *   .setColor(Color.GREEN)
+     *   .setColorized(true)
+     *   .setContentTitle("Arrive 10:08 AM").
+     *   .setContentText("Dominique Ansel Bakery Soho")
+     *   .addAction(new Notification.Action("Exit navigation",...))
+     *   .setStyle(new Notification.ProgressStyle()
+     *       .setStyledByProgress(false)
+     *       .setProgress(456)
+     *       .setProgressTrackerIcon(Icon.createWithResource(R.drawable.ic_driving_tracker))
+     *       .addProgressSegment(new Segment(41).setColor(Color.BLACK))
+     *       .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
+     *       .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
+     *       .addProgressSegment(new Segment(94).setColor(Color.BLUE))
+     *       .addProgressStep(new Step(60).setColor(Color.RED))
+     *       .addProgressStep(new Step(560).setColor(Color.YELLOW))
+     *   )
+     * 
+ * + * + * + * NOTE: The progress bar layout will be mirrored for RTL layout. + * NOTE: The extras set by {@link Notification.Builder#setProgress} will be overridden by + * the values set on this style object when the notification is built. + * + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static class ProgressStyle extends Notification.Style { + private static final String KEY_ELEMENT_STABLE_ID = "stableId"; + private static final String KEY_ELEMENT_COLOR = "colorInt"; + private static final String KEY_SEGMENT_LENGTH = "length"; + private static final String KEY_STEP_POSITION = "position"; + + private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15; + private static final int MAX_PROGRESS_STEP_LIMIT = 5; + private static final int DEFAULT_PROGRESS_MAX = 100; + + private List mProgressSegments = new ArrayList<>(); + private List mProgressSteps = new ArrayList<>(); + + private int mProgress = 0; + + private boolean mIndeterminate; + + private boolean mIsStyledByProgress = true; + + @Nullable + private Icon mTrackerIcon; + @Nullable + private Icon mStartIcon; + @Nullable + private Icon mEndIcon; + + /** + * @hide + */ + @Override + public boolean areNotificationsVisiblyDifferent(Style other) { + if (other == null || getClass() != other.getClass()) { + return true; + } + + final ProgressStyle progressStyle = (ProgressStyle) other; + + /** + * @see #setProgressIndeterminate + */ + if (!Objects.equals(mIndeterminate, progressStyle.mIndeterminate)) { + return true; + } + boolean nonIndeterminateCheckResult = false; + if (!mIndeterminate) { + nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress) + || !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress) + || !Objects.equals(mProgressSegments, progressStyle.mProgressSegments) + || !Objects.equals(mProgressSteps, progressStyle.mProgressSteps) + || !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon); + } + + return !Objects.equals(mStartIcon, progressStyle.mStartIcon) + || !Objects.equals(mEndIcon, progressStyle.mEndIcon) + || nonIndeterminateCheckResult; + } + + /** + * Gets the segments that define the background layer of the progress bar. + * + * If no segments are provided, the progress bar will be rendered with a single segment + * with length 100 and default color. + * + * @see #setProgressSegments + * @see #addProgressSegment + * @see Segment + */ + public @NonNull List getProgressSegments() { + return mProgressSegments; + } + + /** + * Sets or replaces the segments of the progress bar. + * + * Segments allow for creating progress bars with multiple colors or sections + * to represent different stages or categories of progress. + * For example, Traffic conditions along a navigation journey. + * @see Segment + */ + public @NonNull ProgressStyle setProgressSegments(@NonNull List progressSegments) { + mProgressSegments = new ArrayList<>(progressSegments.size()); + return this; + } + + /** + * Appends a segment to the end of the progress bar. + * + * Segments allow for creating progress bars with multiple colors or sections + * to represent different stages or categories of progress. + * For example, Traffic conditions along a navigation journey. + * @see Segment + */ + public @NonNull ProgressStyle addProgressSegment(@NonNull Segment segment) { + if (mProgressSegments == null) { + mProgressSegments = new ArrayList<>(); + } + mProgressSegments.add(segment); + + return this; + } + + /** + * Gets the steps that are displayed on the progress bar. + *. + * @see #setProgressSteps + * @see #addProgressStep + * @see Step + */ + public @NonNull List getProgressSteps() { + return mProgressSteps; + } + + /** + * Replaces all the progress steps. + * + * Steps are designated points within a progressbar to visualize + * distinct stages or milestones. + * For example, you might use steps to mark stops in a multi-stop + * navigation journey, where each step represents a destination. + * @see Step + */ + public @NonNull ProgressStyle setProgressSteps(@NonNull List steps) { + mProgressSteps = new ArrayList<>(steps); + return this; + } + + /** + * Adds another step. + * + * Steps are designated points within a progressbar to visualize + * distinct stages or milestones. + * For example, you might use steps to mark stops in a multi-stop + * navigation journey, where each step represents a destination. + * + * Steps can be added in any order, as their + * position within the progress bar is determined by their individual + * {@link Step#getPosition()}. + * @see Step + */ + public @NonNull ProgressStyle addProgressStep(@NonNull Step step) { + if (mProgressSteps == null) { + mProgressSteps = new ArrayList<>(); + } + mProgressSteps.add(step); + + return this; + } + + /** + * Gets the progress value of the progress bar. + * @see #setProgress + */ + public int getProgress() { + return mProgress; + } + + /** + * Specifies the progress (in the same units as {@link Segment#getLength()}) + * of the tracker along the length of the bar. + * + * The max progress value is the sum of all Segment lengths. + * The default value is 0. + */ + public @NonNull ProgressStyle setProgress(int progress) { + mProgress = progress; + return this; + } + + /** + * Gets the sum of the lengths of all Segments in the style, which + * defines the maximum progress. Defaults to 100 when segments are omitted. + */ + public int getProgressMax() { + final List progressSegment = mProgressSegments; + if (progressSegment == null || progressSegment.isEmpty()) { + return DEFAULT_PROGRESS_MAX; + } else { + int progressMax = 0; + int validSegmentCount = 0; + for (int i = 0; i < progressSegment.size() + && validSegmentCount < MAX_PROGRESS_SEGMENT_LIMIT; i++) { + int segmentLength = progressSegment.get(i).getLength(); + if (segmentLength > 0) { + try { + progressMax = Math.addExact(progressMax, segmentLength); + validSegmentCount++; + } catch (ArithmeticException e) { + Log.e(TAG, + "Notification.ProgressStyle segment total overflowed.", e); + return DEFAULT_PROGRESS_MAX; + } + } + } + + if (validSegmentCount == 0) { + return DEFAULT_PROGRESS_MAX; + } + + return progressMax; + } + + } + + /** + * Get indeterminate value of the progress bar. + * @see #setProgressIndeterminate + */ + public boolean isProgressIndeterminate() { + return mIndeterminate; + } + + /** + * Used to indicate an initialization state without a known progress amount. + * When specified, the following fields are ignored: + * @see #setProgress + * @see #setProgressSegments + * @see #setProgressSteps + * @see #setProgressTrackerIcon + * @see #setStyledByProgress + * + * If the app provides exactly one Segment, that segment's color will be + * used to style the indeterminate bar. + */ + public @NonNull ProgressStyle setProgressIndeterminate(boolean indeterminate) { + mIndeterminate = indeterminate; + return this; + } + + /** + * Gets whether the progress bar's style is based on its progress. + * @see #setStyledByProgress + */ + public boolean isStyledByProgress() { + return mIsStyledByProgress; + } + + /** + * Indicates whether the segments and steps will be styled differently + * based on whether they are behind or ahead of the current progress. + * When true, segments appearing ahead of the current progress will be given a + * slightly different appearance to indicate that it is part of the progress bar + * that is not "filled". + * When false, all segments will be given the filled appearance, and it will be + * the app's responsibility to use #setProgressTrackerIcon or segment colors + * to make the current progress clear to the user. + * the default value is true. + */ + public @NonNull ProgressStyle setStyledByProgress(boolean enabled) { + mIsStyledByProgress = enabled; + return this; + } + + + /** + * Gets the progress tracker icon for the progress bar. + * @see #setProgressTrackerIcon + */ + public @Nullable Icon getProgressTrackerIcon() { + return mTrackerIcon; + } + + /** + * An optional icon that can appear as an overlay on the bar at the point of + * current progress. + * Aspect ratio may be anywhere from 2:1 to 1:2; content outside that + * aspect ratio range will be cropped. + * This icon will be mirrored in RTL. + */ + public @NonNull ProgressStyle setProgressTrackerIcon(@Nullable Icon trackerIcon) { + mTrackerIcon = trackerIcon; + return this; + } + + /** + * Gets the progress bar start icon. + * @see #setProgressStartIcon + */ + public @Nullable Icon getProgressStartIcon() { + return mStartIcon; + } + + /** + * An optional square icon that appears at the start of the progress bar. + * This icon will be cropped to its central square. + * This icon will NOT be mirrored in RTL layouts. + */ + public @NonNull ProgressStyle setProgressStartIcon(@Nullable Icon startIcon) { + mStartIcon = startIcon; + return this; + } + + /** + * Gets the progress bar end icon. + * @see #setProgressEndIcon(Icon) + */ + public @Nullable Icon getProgressEndIcon() { + return mEndIcon; + } + + /** + * An optional square icon that appears at the end of the progress bar. + * This icon will be cropped to its central square. + * This icon will NOT be mirrored in RTL layouts. + */ + public @NonNull ProgressStyle setProgressEndIcon(@Nullable Icon endIcon) { + mEndIcon = endIcon; + return this; + } + + /** + * @hide + */ + @Override + public void purgeResources() { + super.purgeResources(); + if (mTrackerIcon != null) { + mTrackerIcon.convertToAshmem(); + } + if (mStartIcon != null) { + mStartIcon.convertToAshmem(); + } + if (mEndIcon != null) { + mEndIcon.convertToAshmem(); + } + } + + /** + * @hide + */ + @Override + public void reduceImageSizes(Context context) { + super.reduceImageSizes(context); + + final Resources resources = context.getResources(); + + int progressIconSize = + resources.getDimensionPixelSize(R.dimen.notification_progress_icon_size); + if (mStartIcon != null) { + mStartIcon.scaleDownIfNecessary(progressIconSize, progressIconSize); + } + if (mEndIcon != null) { + mEndIcon.scaleDownIfNecessary(progressIconSize, progressIconSize); + } + if (mTrackerIcon != null) { + int progressTrackerWidth = resources.getDimensionPixelSize( + R.dimen.notification_progress_tracker_width); + int progressTrackerHeight = resources.getDimensionPixelSize( + R.dimen.notification_progress_tracker_height); + mTrackerIcon.scaleDownIfNecessary(progressTrackerWidth, progressTrackerHeight); + } + } + + /** + * @hide + */ + @Override + public void addExtras(Bundle extras) { + super.addExtras(extras); + extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS, + getProgressSegmentsAsBundleList(mProgressSegments)); + extras.putParcelableArrayList(EXTRA_PROGRESS_STEPS, + getProgressStepsAsBundleList(mProgressSteps)); + + extras.putInt(EXTRA_PROGRESS, mProgress); + extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate); + extras.putInt(EXTRA_PROGRESS_MAX, getProgressMax()); + extras.putBoolean(EXTRA_STYLED_BY_PROGRESS, mIsStyledByProgress); + + if (mTrackerIcon != null) { + extras.putParcelable(EXTRA_PROGRESS_TRACKER_ICON, mTrackerIcon); + } else { + extras.remove(EXTRA_PROGRESS_TRACKER_ICON); + } + + if (mStartIcon != null) { + extras.putParcelable(EXTRA_PROGRESS_START_ICON, mStartIcon); + } else { + extras.remove(EXTRA_PROGRESS_START_ICON); + } + + if (mEndIcon != null) { + extras.putParcelable(EXTRA_PROGRESS_END_ICON, mEndIcon); + } else { + extras.remove(EXTRA_PROGRESS_END_ICON); + } + } + + /** + * @hide + */ + @Override + protected void restoreFromExtras(Bundle extras) { + super.restoreFromExtras(extras); + mProgressSegments = getProgressSegmentsFromBundleList( + extras.getParcelableArrayList(EXTRA_PROGRESS_SEGMENTS, Bundle.class)); + mProgress = extras.getInt(EXTRA_PROGRESS, 0); + mIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE, false); + mIsStyledByProgress = extras.getBoolean(EXTRA_STYLED_BY_PROGRESS, true); + mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class); + mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class); + mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class); + mProgressSteps = getProgressStepsFromBundleList( + extras.getParcelableArrayList(EXTRA_PROGRESS_STEPS, Bundle.class)); + } + + /** + * @hide + */ + @Override + public boolean displayCustomViewInline() { + // This is a lie; True is returned for progress notifications to make sure + // that the custom view is not used instead of the template, but it will not + // actually be included. + return true; + } + + private static @NonNull ArrayList getProgressSegmentsAsBundleList( + @Nullable List progressSegments) { + final ArrayList segments = new ArrayList<>(); + if (progressSegments != null && !progressSegments.isEmpty()) { + for (int i = 0; i < progressSegments.size(); i++) { + final Segment segment = progressSegments.get(i); + if (segment.getLength() <= 0) { + continue; + } + + final Bundle bundle = new Bundle(); + bundle.putInt(KEY_SEGMENT_LENGTH, segment.getLength()); + bundle.putInt(KEY_ELEMENT_STABLE_ID, segment.getStableId()); + bundle.putInt(KEY_ELEMENT_COLOR, segment.getColor()); + + segments.add(bundle); + } + } + + return segments; + } + + private static @NonNull List getProgressSegmentsFromBundleList( + @Nullable List segmentBundleList) { + final ArrayList segments = new ArrayList<>(); + if (segmentBundleList != null && !segmentBundleList.isEmpty()) { + for (int i = 0; i < segmentBundleList.size(); i++) { + final Bundle segmentBundle = segmentBundleList.get(i); + final int length = segmentBundle.getInt(KEY_SEGMENT_LENGTH); + if (length <= 0) { + continue; + } + + final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID); + final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR, + Notification.COLOR_DEFAULT); + final Segment segment = new Segment(length) + .setStableId(stableId).setColor(color); + + segments.add(segment); + } + } + + return segments; + } + + private static @NonNull ArrayList getProgressStepsAsBundleList( + @Nullable List progressSteps) { + final ArrayList steps = new ArrayList<>(); + if (progressSteps != null && !progressSteps.isEmpty()) { + for (int i = 0; i < progressSteps.size(); i++) { + final Step step = progressSteps.get(i); + if (step.getPosition() < 0) { + continue; + } + + final Bundle bundle = new Bundle(); + bundle.putInt(KEY_STEP_POSITION, step.getPosition()); + bundle.putInt(KEY_ELEMENT_STABLE_ID, step.getStableId()); + bundle.putInt(KEY_ELEMENT_COLOR, step.getColor()); + + steps.add(bundle); + } + } + + return steps; + } + + private static @NonNull List getProgressStepsFromBundleList( + @Nullable List stepBundleList) { + final ArrayList steps = new ArrayList<>(); + + if (stepBundleList != null && !stepBundleList.isEmpty()) { + for (int i = 0; i < stepBundleList.size(); i++) { + final Bundle segmentBundle = stepBundleList.get(i); + final int position = segmentBundle.getInt(KEY_STEP_POSITION); + if (position < 0) { + continue; + } + final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID); + final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR, + Notification.COLOR_DEFAULT); + final Step step = new Step(position).setStableId(stableId).setColor(color); + steps.add(step); + } + } + + return steps; + } + + /** + * A segment of the progress bar, which defines its length and color. + * Segments allow for creating progress bars with multiple colors or sections + * to represent different stages or categories of progress. + * For example, Traffic conditions along a navigation journey. + */ + public static final class Segment { + private int mLength; + private int mStableId = 0; + @ColorInt + private int mColor = Notification.COLOR_DEFAULT; + + /** + * Create a segment with a non-zero length. + * @param length + * See {@link #getLength} + */ + public Segment(int length) { + mLength = length; + } + + /** + * The length of this Segment within the progress bar. + * This value has no units, it is just relative to the length of other segments, + * and the value provided to {@link ProgressStyle#setProgress}. + */ + public int getLength() { + return mLength; + } + + /** + * Gets the stable id of this Segment. + * + * @see #setStableId + */ + public int getStableId() { + return mStableId; + } + + /** + * Optional ID used to uniquely identify the element across updates. + */ + public @NonNull Segment setStableId(int stableId) { + mStableId = stableId; + return this; + } + + /** + * Returns the color of this Segment. + * + * @see #setColor + */ + @ColorInt + public int getColor() { + return mColor; + } + + /** + * Optional color of this Segment + */ + public @NonNull Segment setColor(@ColorInt int color) { + mColor = color; + return this; + } + + /** + * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent} + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Segment segment = (Segment) o; + return mLength == segment.mLength && mStableId == segment.mStableId + && mColor == segment.mColor; + } + + @Override + public int hashCode() { + return Objects.hash(mLength, mStableId, mColor); + } + } + + /** + * A step within the progress bar, defining its position and color. + * Steps are designated points within a progressbar to visualize + * distinct stages or milestones. + * For example, you might use steps to mark stops in a multi-stop + * navigation journey, where each step represents a destination. + */ + public static final class Step { + + private int mPosition; + private int mStableId; + @ColorInt + private int mColor = Notification.COLOR_DEFAULT; + + /** + * Create a step element. + * The position of this step on the progress bar + * relative to {@link ProgressStyle#getProgressMax} + * @param position + * See {@link #getPosition} + */ + public Step(int position) { + mPosition = position; + } + + /** + * Gets the position of this Step. + * The position of this step on the progress bar + * relative to {@link ProgressStyle#getProgressMax}. + */ + public int getPosition() { + return mPosition; + } + + + /** + * Optional ID used to uniqurely identify the element across updates. + */ + public int getStableId() { + return mStableId; + } + + /** + * Optional ID used to uniqurely identify the element across updates. + */ + public @NonNull Step setStableId(int stableId) { + mStableId = stableId; + return this; + } + + /** + * Returns the color of this Segment. + * + * @see #setColor + */ + @ColorInt + public int getColor() { + return mColor; + } + + /** + * Optional color of this Segment + */ + public @NonNull Step setColor(@ColorInt int color) { + mColor = color; + return this; + } + + /** + * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent} + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Step step = (Step) o; + return mPosition == step.mPosition && mStableId == step.mStableId + && mColor == step.mColor; + } + + @Override + public int hashCode() { + return Objects.hash(mPosition, mStableId, mColor); + } + } + } + /** * Notification style for custom views that are decorated by the system * diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 83f9ff733ec8bd9b91da89f21fb88681e20679e8..c7b84ae6283b014a99ac4323fbb130af9b474526 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -952,6 +952,36 @@ public class NotificationManager { } } + /** + * Returns whether the calling app's properly formatted notifications can appear in a promoted + * format, which may result in higher ranking, appearances on additional surfaces, and richer + * presentation. + */ + @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING) + public boolean canPostPromotedNotifications() { + INotificationManager service = getService(); + try { + return service.canBePromoted(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Setter for {@link #canPostPromotedNotifications()}. Only callable by the OS. + * @hide + */ + @TestApi + @FlaggedApi(android.app.Flags.FLAG_API_RICH_ONGOING) + public void setCanPostPromotedNotifications(@NonNull String pkg, int uid, boolean allowed) { + INotificationManager service = getService(); + try { + service.setCanBePromoted(pkg, uid, allowed, true); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Creates a group container for {@link NotificationChannel} objects. * diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index cd7e40cf174d1894a1decdaba8080040e5fcd3dc..d363e19bcc195e63aff54fb2f375a731bb16c105 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -38,6 +38,7 @@ per-file GameState* = file:/GAME_MANAGER_OWNERS per-file IGameManager* = file:/GAME_MANAGER_OWNERS per-file IGameMode* = file:/GAME_MANAGER_OWNERS per-file BackgroundStartPrivileges.java = file:/BAL_OWNERS +per-file activity_manager.aconfig = file:/ACTIVITY_MANAGER_OWNERS # ActivityThread per-file ActivityThread.java = file:/services/core/java/com/android/server/am/OWNERS diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 0e761fce9346b1cad62cf59b7a0729b2ea1c943f..c17da249f322be3d87171ddd796442d405a02a74 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -54,198 +54,8 @@ import java.util.concurrent.atomic.AtomicLong; * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing, * but doesn't hold a lock across data fetches on query misses. * - * The intended use case is caching frequently-read, seldom-changed information normally - * retrieved across interprocess communication. Imagine that you've written a user birthday - * information daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface - * over binder. That binder interface looks something like this: - * - *
- * parcelable Birthday {
- *   int month;
- *   int day;
- * }
- * interface IUserBirthdayService {
- *   Birthday getUserBirthday(int userId);
- * }
- * 
- * - * Suppose the service implementation itself looks like this... - * - *
- * public class UserBirthdayServiceImpl implements IUserBirthdayService {
- *   private final HashMap<Integer, Birthday%> mUidToBirthday;
- *   {@literal @}Override
- *   public synchronized Birthday getUserBirthday(int userId) {
- *     return mUidToBirthday.get(userId);
- *   }
- *   private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) {
- *     mUidToBirthday.clear();
- *     mUidToBirthday.putAll(uidToBirthday);
- *   }
- * }
- * 
- * - * ... and we have a client in frameworks (loaded into every app process) that looks - * like this: - * - *
- * public class ActivityThread {
- *   ...
- *   public Birthday getUserBirthday(int userId) {
- *     return GetService("birthdayd").getUserBirthday(userId);
- *   }
- *   ...
- * }
- * 
- * - * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call - * to the birthdayd process and consult its database of birthdays. If we query user birthdays - * frequently, we do a lot of work that we don't have to do, since user birthdays - * change infrequently. - * - * PropertyInvalidatedCache is part of a pattern for optimizing this kind of - * information-querying code. Using {@code PropertyInvalidatedCache}, you'd write the client - * this way: - * - *
- * public class ActivityThread {
- *   ...
- *   private final PropertyInvalidatedCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
- *       new PropertyInvalidatedCache.QueryHandler<Integer, Birthday>() {
- *           {@literal @}Override
- *           public Birthday apply(Integer) {
- *              return GetService("birthdayd").getUserBirthday(userId);
- *           }
- *       };
- *   private static final int BDAY_CACHE_MAX = 8;  // Maximum birthdays to cache
- *   private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
- *   private final PropertyInvalidatedCache<Integer, Birthday%> mBirthdayCache = new
- *     PropertyInvalidatedCache<Integer, Birthday%>(
- *             BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery);
- *
- *   public void disableUserBirthdayCache() {
- *     mBirthdayCache.disableForCurrentProcess();
- *   }
- *   public void invalidateUserBirthdayCache() {
- *     mBirthdayCache.invalidateCache();
- *   }
- *   public Birthday getUserBirthday(int userId) {
- *     return mBirthdayCache.query(userId);
- *   }
- *   ...
- * }
- * 
- * - * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday - * for the first time; on subsequent queries, we return the already-known Birthday object. - * - * The second parameter to the IpcDataCache constructor is a string that identifies the "module" - * that owns the cache. There are some well-known modules (such as {@code MODULE_SYSTEM} but any - * string is permitted. The third parameters is the name of the API being cached; this, too, can - * any value. The fourth is the name of the cache. The cache is usually named after th API. - * Some things you must know about the three strings: - * - *
    The system property that controls the cache is named {@code cache_key..}. - * Usually, the SELinux rules permit a process to write a system property (and therefore - * invalidate a cache) based on the wildcard {@code cache_key..*}. This means that - * although the cache can be constructed with any module string, whatever string is chosen must be - * consistent with the SELinux configuration. - *
      The API name can be any string of alphanumeric characters. All caches with the same API - * are invalidated at the same time. If a server supports several caches and all are invalidated - * in common, then it is most efficient to assign the same API string to every cache. - *
        The cache name can be any string. In debug output, the name is used to distiguish between - * caches with the same API name. The cache name is also used when disabling caches in the - * current process. So, invalidation is based on the module+api but disabling (which is generally - * a once-per-process operation) is based on the cache name. - * - * - * User birthdays do occasionally change, so we have to modify the server to invalidate this - * cache when necessary. That invalidation code looks like this: - * - *
        - * public class UserBirthdayServiceImpl {
        - *   ...
        - *   public UserBirthdayServiceImpl() {
        - *     ...
        - *     ActivityThread.currentActivityThread().disableUserBirthdayCache();
        - *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
        - *   }
        - *
        - *   private synchronized void updateBirthdays(Map<Integer, Birthday%> uidToBirthday) {
        - *     mUidToBirthday.clear();
        - *     mUidToBirthday.putAll(uidToBirthday);
        - *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
        - *   }
        - *   ...
        - * }
        - * 
        - * - * The call to {@code PropertyInvalidatedCache.invalidateCache()} guarantees that all clients - * will re-fetch birthdays from binder during consequent calls to - * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock - * held, we maintain consistency between different client views of the birthday state. The use - * of PropertyInvalidatedCache in this idiomatic way introduces no new race conditions. - * - * PropertyInvalidatedCache has a few other features for doing things like incremental - * enhancement of cached values and invalidation of multiple caches (that all share the same - * property key) at once. - * - * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each - * time we update the cache. SELinux configuration must allow everyone to read this property - * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write - * the property. (These properties conventionally begin with the "cache_key." prefix.) - * - * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so - * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In - * this local case, there's no IPC, so use of the cache is (depending on exact - * circumstance) unnecessary. - * - * There may be queries for which it is more efficient to bypass the cache than to cache - * the result. This would be true, for example, if some queries would require frequent - * cache invalidation while other queries require infrequent invalidation. To expand on - * the birthday example, suppose that there is a userId that signifies "the next - * birthday". When passed this userId, the server returns the next birthday among all - * users - this value changes as time advances. The userId value can be cached, but the - * cache must be invalidated whenever a birthday occurs, and this invalidates all - * birthdays. If there is a large number of users, invalidation will happen so often that - * the cache provides no value. - * - * The class provides a bypass mechanism to handle this situation. - *
        - * public class ActivityThread {
        - *   ...
        - *   private final IpcDataCache.QueryHandler<Integer, Birthday> mBirthdayQuery =
        - *       new IpcDataCache.QueryHandler<Integer, Birthday>() {
        - *           {@literal @}Override
        - *           public Birthday apply(Integer) {
        - *              return GetService("birthdayd").getUserBirthday(userId);
        - *           }
        - *           {@literal @}Override
        - *           public boolean shouldBypassQuery(Integer userId) {
        - *               return userId == NEXT_BIRTHDAY;
        - *           }
        - *       };
        - *   ...
        - * }
        - * 
        - * - * If the {@code shouldBypassQuery()} method returns true then the cache is not used for that - * particular query. The {@code shouldBypassQuery()} method is not abstract and the default - * implementation returns false. - * - * For security, there is a allowlist of processes that are allowed to invalidate a cache. - * The allowlist includes normal runtime processes but does not include test processes. - * Test processes must call {@code PropertyInvalidatedCache.disableForTestMode()} to disable - * all cache activity in that process. - * - * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding. - * - * To test a binder cache, create one or more tests that exercise the binder method. This - * should be done twice: once with production code and once with a special image that sets - * {@code DEBUG} and {@code VERIFY} true. In the latter case, verify that no cache - * inconsistencies are reported. If a cache inconsistency is reported, however, it might be a - * false positive. This happens if the server side data can be read and written non-atomically - * with respect to cache invalidation. + * This interface is deprecated. New clients should use {@link IpcDataCache} instead. Internally, + * that class uses {@link PropertyInvalidatedCache} , but that design may change in the future. * * @param The class used to index cache entries: must be hashable and comparable * @param The class holding cache entries; use a boxed primitive if possible diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 03bec71548a8be4a21a49bb894df59fd32b96801..ea4148c8ffa19d712a43c26ce01d6fa27b946c75 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -17,6 +17,7 @@ package android.app; import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; +import static android.server.Flags.removeGameManagerServiceFromWear; import android.accounts.AccountManager; import android.accounts.IAccountManager; @@ -74,6 +75,7 @@ import android.companion.virtual.IVirtualDeviceManager; import android.companion.virtual.VirtualDeviceManager; import android.compat.Compatibility; import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledSince; import android.content.ClipboardManager; import android.content.ContentCaptureOptions; @@ -228,6 +230,7 @@ import android.print.IPrintManager; import android.print.PrintManager; import android.provider.E2eeContactKeysManager; import android.provider.ProviderFrameworkInitializer; +import android.ranging.RangingFrameworkInitializer; import android.safetycenter.SafetyCenterFrameworkInitializer; import android.scheduling.SchedulingFrameworkInitializer; import android.security.FileIntegrityManager; @@ -308,6 +311,16 @@ public final class SystemServiceRegistry { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) static final long ENABLE_CHECKING_TELEPHONY_FEATURES_FOR_VCN = 330902016; + /** + * After {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, Wear devices will be allowed to publish + * no {@link GameManager} instance. This is because the respective system service is no longer + * started for Wear devices given that the applications of the service do not currently apply to + * Wear. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + static final long NULL_GAME_MANAGER_IN_WEAR = 340929737; + /** * The corresponding vendor API for Android V * @@ -1624,8 +1637,24 @@ public final class SystemServiceRegistry { @Override public GameManager createService(ContextImpl ctx) throws ServiceNotFoundException { - return new GameManager(ctx.getOuterContext(), - ctx.mMainThread.getHandler()); + final PackageManager pm = ctx.getPackageManager(); + final boolean isWatch = pm.hasSystemFeature(PackageManager.FEATURE_WATCH); + final IBinder binder = + // Allow a potentially absent GameManagerService only for + // Wear devices. For non-Wear devices, throw a + // ServiceNotFoundException when the service is missing. + (removeGameManagerServiceFromWear() && isWatch) + ? ServiceManager.getService(Context.GAME_SERVICE) + : ServiceManager.getServiceOrThrow(Context.GAME_SERVICE); + + if (binder == null + && Compatibility.isChangeEnabled(NULL_GAME_MANAGER_IN_WEAR)) { + return null; + } + + return new GameManager( + ctx.getOuterContext(), + IGameManagerService.Stub.asInterface(binder)); } }); @@ -1797,6 +1826,12 @@ public final class SystemServiceRegistry { if (android.webkit.Flags.updateServiceIpcWrapper()) { WebViewBootstrapFrameworkInitializer.registerServiceWrappers(); } + // This is guarded by aconfig flag "com.android.ranging.flags.ranging_stack_enabled" + // when the build flag RELEASE_RANGING_STACK is enabled. When disabled, this calls the + // mock RangingFrameworkInitializer#registerServiceWrappers which is no-op. As the + // aconfig lib for ranging module is built only if RELEASE_RANGING_STACK is enabled, + // flagcannot be added here. + RangingFrameworkInitializer.registerServiceWrappers(); } finally { // If any of the above code throws, we're in a pretty bad shape and the process // will likely crash, but we'll reset it just in case there's an exception handler... diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 5ed1f4e355335d2ce6c43c521832c8b5524b3042..637187e01160f3bcd6611915ad3db332a28f7fdb 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -177,6 +177,10 @@ { "file_patterns": ["(/|^)AppOpsManager.java"], "name": "CtsAppOpsTestCases" + }, + { + "file_patterns": ["(/|^)BroadcastStickyCache.java"], + "name": "BroadcastUnitTests" } ] } diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 38bd576d607a18a4adaf0b54bcca5b09de7a902c..1f31ab5d1849cd27db9e01cd3ffed1b8d5a8757a 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -147,3 +147,21 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "use_sticky_bcast_cache" + description: "Use cache for sticky broadcast intents" + is_fixed_read_only: true + bug: "356148006" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "system_performance" + name: "app_start_info_component" + description: "Control ApplicationStartInfo component field and API" + bug: "362537357" +} diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index daa15f05d942e6682edca47b001bd2bbd4e44c8e..9be928f7efd0ebc835b37fcf6c86ddb42392181e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -213,19 +213,17 @@ import java.util.function.Consumer; * Device Administration * developer guide. * - *

        Through Managed Provisioning, - * Device Administrator apps can also be recognised as - Device Policy Controllers. Device Policy Controllers can be one of + *

        Device Administrator apps can also be recognised as + * Device Policy Controllers. Device Policy Controllers can be one of * two types: *

          *
        • A Device Owner, which only ever exists on the - * {@link UserManager#isSystemUser System User} or {@link UserManager#isMainUser Main User}, is + * {@link UserManager#isSystemUser System User} or Main User, is * the most powerful type of Device Policy Controller and can affect policy across the device. *
        • A Profile Owner, which can exist on any user, can * affect policy on the user it is on, and when it is running on * {@link UserManager#isProfile a profile} has - * limited ability to affect policy on its - * {@link UserManager#getProfileParent parent}. + * limited ability to affect policy on its parent. *
        * *

        Additional capabilities can be provided to Device Policy Controllers in @@ -233,7 +231,7 @@ import java.util.function.Consumer; *

          *
        • A Profile Owner on an organization owned device has access * to additional abilities, both affecting policy on the profile's - * {@link UserManager#getProfileParent parent} and also the profile itself. + * parent and also the profile itself. *
        • A Profile Owner running on the {@link UserManager#isSystemUser System User} has access to * additional capabilities which affect the {@link UserManager#isSystemUser System User} and * also the whole device. @@ -245,13 +243,12 @@ import java.util.function.Consumer; * Controller. * *

          Permissions are generally only given to apps - * fulfilling particular key roles on the device (such as managing {@link DeviceLockManager -device locks}). + * fulfilling particular key roles on the device (such as managing + * {@link android.devicelock.DeviceLockManager device locks}). * *

          Device Policy Management Role Holder - *

          One app on the device fulfills the {@link RoleManager#ROLE_DEVICE_POLICY_MANAGEMENT Device -Policy Management Role} and is trusted with managing the overall state of - * Device Policy. This has access to much more powerful methods than + *

          One app on the device fulfills the Device Policy Management Role and is trusted with managing + * the overall state of Device Policy. This has access to much more powerful methods than * managing apps. * *

          Querying Device Policy @@ -273,7 +270,7 @@ Policy Management Role} and is trusted with managing the overall state of * *

          A Managed Profile enables data separation. For example to use * a device both for personal and corporate usage. The managed profile and its - * {@link UserManager#getProfileParent parent} share a launcher. + * parent share a launcher. * *

          Affiliation *

          Using the {@link #setAffiliationIds} method, a @@ -6643,7 +6640,7 @@ public class DevicePolicyManager { * @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}. * @throws SecurityException if the calling application does not own an active administrator * that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and the does not hold - * the {@link android.Manifest.permission#LOCK_DEVICE} permission, or + * the LOCK_DEVICE permission, or * the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is passed by an * application that is not a profile owner of a managed profile. * @throws IllegalArgumentException if the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 4682f3d30e1eeb83aae9e4e63648ff5035380e9c..216ba5d994eccd6635132e701caa88ec6096dde9 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -27,6 +27,8 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.content.Context; +import android.os.CancellationSignal; +import android.os.ICancellationSignal; import android.os.RemoteException; import java.util.Objects; @@ -73,7 +75,43 @@ public final class AppFunctionManager { * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code * android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code * ExecuteAppFunctionResponse.RESULT_DENIED}. + * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor, + * CancellationSignal, Consumer)} instead. This method will be removed once usage references + * are updated. */ + @RequiresPermission( + anyOf = { + Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, + Manifest.permission.EXECUTE_APP_FUNCTIONS + }, + conditional = true) + @UserHandleAware + @Deprecated + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer callback) { + executeAppFunction(request, executor, new CancellationSignal(), callback); + } + + /** + * Executes the app function. + * + *

          Note: Applications can execute functions they define. To execute functions defined in + * another component, apps would need to have {@code + * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code + * android.permission.EXECUTE_APP_FUNCTIONS}. + * + * @param request the request to execute the app function + * @param executor the executor to run the callback + * @param cancellationSignal the cancellation signal to cancel the execution. + * @param callback the callback to receive the function execution result. if the calling app + * does not own the app function or does not have {@code + * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code + * android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code + * ExecuteAppFunctionResponse.RESULT_DENIED}. + */ + // TODO(b/357551503): Document the behavior when the cancellation signal is issued. // TODO(b/360864791): Document that apps can opt-out from being executed by callers with // EXECUTE_APP_FUNCTIONS and how a caller knows whether a function is opted out. // TODO(b/357551503): Update documentation when get / set APIs are implemented that this will @@ -88,6 +126,7 @@ public final class AppFunctionManager { public void executeAppFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull @CallbackExecutor Executor executor, + @NonNull CancellationSignal cancellationSignal, @NonNull Consumer callback) { Objects.requireNonNull(request); Objects.requireNonNull(executor); @@ -96,25 +135,31 @@ public final class AppFunctionManager { ExecuteAppFunctionAidlRequest aidlRequest = new ExecuteAppFunctionAidlRequest( request, mContext.getUser(), mContext.getPackageName()); + try { - mService.executeAppFunction( - aidlRequest, - new IExecuteAppFunctionCallback.Stub() { - @Override - public void onResult(ExecuteAppFunctionResponse result) { - try { - executor.execute(() -> callback.accept(result)); - } catch (RuntimeException e) { - // Ideally shouldn't happen since errors are wrapped into the - // response, but we catch it here for additional safety. - callback.accept( - ExecuteAppFunctionResponse.newFailure( - getResultCode(e), - e.getMessage(), - /* extras= */ null)); - } - } - }); + ICancellationSignal cancellationTransport = + mService.executeAppFunction( + aidlRequest, + new IExecuteAppFunctionCallback.Stub() { + @Override + public void onResult(ExecuteAppFunctionResponse result) { + try { + executor.execute(() -> callback.accept(result)); + } catch (RuntimeException e) { + // Ideally shouldn't happen since errors are wrapped into + // the + // response, but we catch it here for additional safety. + callback.accept( + ExecuteAppFunctionResponse.newFailure( + getResultCode(e), + e.getMessage(), + /* extras= */ null)); + } + } + }); + if (cancellationTransport != null) { + cancellationSignal.setRemote(cancellationTransport); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index 0d981ea5a679eb31cdbd8607c24b3a001f3a28c8..8e417737515e8e9b9884632dcf2501b8942e561b 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -29,7 +29,12 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.CancellationSignal; +import android.os.RemoteCallback; +import android.os.RemoteException; import java.util.function.Consumer; @@ -74,6 +79,7 @@ public abstract class AppFunctionService extends Service { */ void perform( @NonNull ExecuteAppFunctionRequest request, + @NonNull CancellationSignal cancellationSignal, @NonNull Consumer callback); } @@ -85,6 +91,7 @@ public abstract class AppFunctionService extends Service { @Override public void executeAppFunction( @NonNull ExecuteAppFunctionRequest request, + @NonNull ICancellationCallback cancellationCallback, @NonNull IExecuteAppFunctionCallback callback) { if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE) == PERMISSION_DENIED) { @@ -93,7 +100,10 @@ public abstract class AppFunctionService extends Service { SafeOneTimeExecuteAppFunctionCallback safeCallback = new SafeOneTimeExecuteAppFunctionCallback(callback); try { - onExecuteFunction.perform(request, safeCallback::onResult); + onExecuteFunction.perform( + request, + buildCancellationSignal(cancellationCallback), + safeCallback::onResult); } catch (Exception ex) { // Apps should handle exceptions. But if they don't, report the error on // behalf of them. @@ -105,6 +115,21 @@ public abstract class AppFunctionService extends Service { }; } + private static CancellationSignal buildCancellationSignal( + @NonNull ICancellationCallback cancellationCallback) { + final ICancellationSignal cancellationSignalTransport = + CancellationSignal.createTransport(); + CancellationSignal cancellationSignal = + CancellationSignal.fromTransport(cancellationSignalTransport); + try { + cancellationCallback.sendCancellationTransport(cancellationSignalTransport); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return cancellationSignal ; + } + private final Binder mBinder = createBinder( AppFunctionService.this, AppFunctionService.this::onExecuteFunction); @@ -115,6 +140,7 @@ public abstract class AppFunctionService extends Service { return mBinder; } + /** * Called by the system to execute a specific app function. * @@ -134,9 +160,45 @@ public abstract class AppFunctionService extends Service { * * @param request The function execution request. * @param callback A callback to report back the result. + * + * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, + * Consumer)} instead. This method will be removed once usage references are updated. */ @MainThread + @Deprecated public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull Consumer callback); + + /** + * Called by the system to execute a specific app function. + * + *

          This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + *

          To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + *

          This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * + *

          This method also accepts a {@link CancellationSignal} that the app should listen to cancel + * the execution of function if requested by the system. + * + * @param request The function execution request. + * @param cancellationSignal A signal to cancel the execution. + * @param callback A callback to report back the result. + */ + @MainThread + public void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull CancellationSignal cancellationSignal, + @NonNull Consumer callback) { + onExecuteFunction(request, callback); + } } diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java index 84b1837f4a2fcae666556984f10184d84fb6d99c..b29b64e44d21cea1bfd203e8a9eac33aa47848c9 100644 --- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java +++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java @@ -16,10 +16,13 @@ package android.app.appfunctions; +import android.annotation.Nullable; import android.app.appsearch.GenericDocument; import android.os.Parcel; import android.os.Parcelable; +import android.util.MathUtils; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import java.util.Objects; @@ -31,24 +34,33 @@ import java.util.Objects; *

          {#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder * directly or Android shared memory if the data is large. * + *

          This class performs lazy unparcelling. The `GenericDocument` is only unparcelled + * from the underlying `Parcel` when {@link #getValue()} is called. This optimization + * allows the system server to pass through the generic document, without unparcel and parcel it. + * * @hide * @see Parcel#writeBlob(byte[]) */ public final class GenericDocumentWrapper implements Parcelable { + @Nullable + @GuardedBy("mLock") + private GenericDocument mGenericDocument; + @GuardedBy("mLock") + @Nullable private Parcel mParcel; + private final Object mLock = new Object(); + public static final Creator CREATOR = new Creator<>() { @Override public GenericDocumentWrapper createFromParcel(Parcel in) { - byte[] dataBlob = Objects.requireNonNull(in.readBlob()); - Parcel unmarshallParcel = Parcel.obtain(); - try { - unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); - unmarshallParcel.setDataPosition(0); - return new GenericDocumentWrapper( - GenericDocument.createFromParcel(unmarshallParcel)); - } finally { - unmarshallParcel.recycle(); - } + int length = in.readInt(); + int offset = in.dataPosition(); + in.setDataPosition(MathUtils.addOrThrow(offset, length)); + + Parcel p = Parcel.obtain(); + p.appendFrom(in, offset, length); + p.setDataPosition(0); + return new GenericDocumentWrapper(p); } @Override @@ -56,16 +68,42 @@ public final class GenericDocumentWrapper implements Parcelable { return new GenericDocumentWrapper[size]; } }; - @NonNull private final GenericDocument mGenericDocument; public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) { mGenericDocument = Objects.requireNonNull(genericDocument); + mParcel = null; + } + + public GenericDocumentWrapper(@NonNull Parcel parcel) { + mGenericDocument = null; + mParcel = Objects.requireNonNull(parcel); } /** Returns the wrapped {@link android.app.appsearch.GenericDocument} */ @NonNull public GenericDocument getValue() { - return mGenericDocument; + unparcel(); + synchronized (mLock) { + return Objects.requireNonNull(mGenericDocument); + } + } + + private void unparcel() { + synchronized (mLock) { + if (mGenericDocument != null) { + return; + } + byte[] dataBlob = Objects.requireNonNull(Objects.requireNonNull(mParcel).readBlob()); + Parcel unmarshallParcel = Parcel.obtain(); + try { + unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); + unmarshallParcel.setDataPosition(0); + mGenericDocument = GenericDocument.createFromParcel(unmarshallParcel); + mParcel = null; + } finally { + unmarshallParcel.recycle(); + } + } } @Override @@ -75,13 +113,32 @@ public final class GenericDocumentWrapper implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - Parcel parcel = Parcel.obtain(); - try { - mGenericDocument.writeToParcel(parcel, flags); - byte[] bytes = parcel.marshall(); - dest.writeBlob(bytes); - } finally { - parcel.recycle(); + synchronized (mLock) { + if (mGenericDocument != null) { + int lengthPos = dest.dataPosition(); + // write a placeholder for length + dest.writeInt(-1); + Parcel tempParcel = Parcel.obtain(); + byte[] bytes; + try { + mGenericDocument.writeToParcel(tempParcel, flags); + bytes = tempParcel.marshall(); + } finally { + tempParcel.recycle(); + } + int startPos = dest.dataPosition(); + dest.writeBlob(bytes); + int endPos = dest.dataPosition(); + dest.setDataPosition(lengthPos); + // Overwrite the length placeholder + dest.writeInt(endPos - startPos); + dest.setDataPosition(endPos); + + } else { + Parcel originalParcel = Objects.requireNonNull(mParcel); + dest.writeInt(originalParcel.dataSize()); + dest.appendFrom(originalParcel, 0, originalParcel.dataSize()); + } } } } diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl index 28827bb3052c0895636120602b3dd0791324808e..c63217ffe850e9ca4490eee4328ca97ba8bf72f3 100644 --- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl +++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl @@ -18,6 +18,7 @@ package android.app.appfunctions; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.IExecuteAppFunctionCallback; +import android.os.ICancellationSignal; /** * Defines the interface for apps to interact with the app function execution service @@ -32,8 +33,8 @@ interface IAppFunctionManager { * @param callback the callback to report the result. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)") - void executeAppFunction( + ICancellationSignal executeAppFunction( in ExecuteAppFunctionAidlRequest request, in IExecuteAppFunctionCallback callback ); -} \ No newline at end of file +} diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl index cc5a20cfa194c39f907e16e081b08e765beb042c..291f33ccb1b84b5dbc62ba58f2a309df31df7df1 100644 --- a/core/java/android/app/appfunctions/IAppFunctionService.aidl +++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl @@ -16,7 +16,7 @@ package android.app.appfunctions; -import android.os.Bundle; +import android.app.appfunctions.ICancellationCallback; import android.app.appfunctions.IExecuteAppFunctionCallback; import android.app.appfunctions.ExecuteAppFunctionRequest; @@ -34,10 +34,12 @@ oneway interface IAppFunctionService { * Called by the system to execute a specific app function. * * @param request the function execution request. + * @param cancellationCallback a callback to send back the cancellation transport. * @param callback a callback to report back the result. */ void executeAppFunction( in ExecuteAppFunctionRequest request, + in ICancellationCallback cancellationCallback, in IExecuteAppFunctionCallback callback ); } diff --git a/core/java/android/app/appfunctions/ICancellationCallback.aidl b/core/java/android/app/appfunctions/ICancellationCallback.aidl new file mode 100644 index 0000000000000000000000000000000000000000..03235aca017a1aceb917042a798a9d92a7eefe8f --- /dev/null +++ b/core/java/android/app/appfunctions/ICancellationCallback.aidl @@ -0,0 +1,24 @@ +/* + * 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 android.os.ICancellationSignal; + +/** {@hide} */ +oneway interface ICancellationCallback { + void sendCancellationTransport(in ICancellationSignal cancellationTransport); +} \ No newline at end of file diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 9b06adf4e89461a7aab71b66cc7822399dadfc19..b139017219094c08934b317bc935e5dd1da07b6c 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -30,6 +30,16 @@ flag { } } +flag { + name: "modes_ui_empty_shade" + namespace: "systemui" + description: "Shows mode that is currently blocking notifications in the empty shade; dependent on flags modes_api and modes_ui" + bug: "366003631" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "modes_ui_test" namespace: "systemui" @@ -104,6 +114,13 @@ flag { bug: "339523906" } +flag { + name: "notification_no_custom_view_conversations" + namespace: "systemui" + description: "Ensures that conversations are not allowed to use Custom Views." + bug: "368817201" +} + flag { name: "keyguard_private_notifications" namespace: "systemui" @@ -244,4 +261,4 @@ flag { namespace: "systemui" description: "Guards new android.app.richongoingnotification promotion and new uis" bug: "337261753" -} \ No newline at end of file +} diff --git a/core/java/android/app/supervision/ISupervisionManager.aidl b/core/java/android/app/supervision/ISupervisionManager.aidl index 8d25cad2fc673675e1dd9e7d960e528364775e0d..4598421eb3bc71eb2b70f36cd9729c4612c48362 100644 --- a/core/java/android/app/supervision/ISupervisionManager.aidl +++ b/core/java/android/app/supervision/ISupervisionManager.aidl @@ -21,5 +21,5 @@ package android.app.supervision; * {@hide} */ interface ISupervisionManager { - boolean isSupervisionEnabled(); + boolean isSupervisionEnabledForUser(int userId); } diff --git a/core/java/android/app/supervision/OWNERS b/core/java/android/app/supervision/OWNERS index afc549517abee27c3016794617597b5429b69f49..4785a72eda27899dba2ef08685214c48b7539943 100644 --- a/core/java/android/app/supervision/OWNERS +++ b/core/java/android/app/supervision/OWNERS @@ -1,2 +1,2 @@ jparks@google.com -romkal@google.com +vtrmc@google.com diff --git a/core/java/android/app/supervision/SupervisionManager.java b/core/java/android/app/supervision/SupervisionManager.java index 8611a92074c006df0e746e17b566254852f2723a..aee1cd9b47608c1efc99cad28ae780111ef611cf 100644 --- a/core/java/android/app/supervision/SupervisionManager.java +++ b/core/java/android/app/supervision/SupervisionManager.java @@ -17,6 +17,7 @@ package android.app.supervision; import android.annotation.SystemService; +import android.annotation.UserHandleAware; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.RemoteException; @@ -45,13 +46,12 @@ public class SupervisionManager { * * @hide */ + @UserHandleAware public boolean isSupervisionEnabled() { try { - return mService.isSupervisionEnabled(); + return mService.isSupervisionEnabledForUser(mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } - - } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index abb562d8ddaf7687a7f6b7413338146b5a1a0b4f..d8142fd9687c562d7e10ce3ab444eaa05e7571c2 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -1042,10 +1042,11 @@ public class AppWidgetManager { } /** - * Get the available info about the AppWidget. + * Returns the {@link AppWidgetProviderInfo} for the specified AppWidget. * - * @return A appWidgetId. If the appWidgetId has not been bound to a provider yet, or - * you don't have access to that appWidgetId, null is returned. + * @return Information regarding the provider of speficied widget, returns null if the + * appWidgetId has not been bound to a provider yet, or you don't have access + * to that widget. */ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { if (mService == null) { @@ -1390,7 +1391,7 @@ public class AppWidgetManager { * * @param provider The {@link ComponentName} for the {@link * android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget. - * @param extras In not null, this is passed to the launcher app. For eg {@link + * @param extras IF not null, this is passed to the launcher app. e.g. {@link * #EXTRA_APPWIDGET_PREVIEW} can be used for a custom preview. * @param successCallback If not null, this intent will be sent when the widget is created. * diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig index 7117f250d1d5b34efb2ce8676ce79372820f5b07..ac9263c2cab5114ead0611e1c544598df427397a 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -63,4 +63,14 @@ flag { namespace: "app_widgets" description: "Remote document support features in Q2 2025 release" bug: "339721781" -} \ No newline at end of file +} + +flag { + name: "security_policy_interact_across_users" + namespace: "app_widgets" + description: "Allow packages with interact_across_users permission to manage app widgets on behalf of other users." + bug: "357621815" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 031380dc1962a5fa26538158f54a327ae5d2a611..044178c4f6aad9e00c3b7a608504c7bda16813b7 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -20,6 +20,7 @@ import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_AC import static android.content.ContentProvider.maybeAddUserId; import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.security.Flags.FLAG_FRP_ENFORCEMENT; +import static android.security.Flags.preventIntentRedirect; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -7687,9 +7688,17 @@ public class Intent implements Parcelable, Cloneable { /** @hide */ public static final int LOCAL_FLAG_FROM_SYSTEM = 1 << 5; + /** + * This flag indicates the creator token of this intent has been verified. + * + * @hide + */ + public static final int LOCAL_FLAG_CREATOR_TOKEN_VERIFIED = 1 << 6; + /** @hide */ @IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = { EXTENDED_FLAG_FILTER_MISMATCH, + EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN, }) @Retention(RetentionPolicy.SOURCE) public @interface ExtendedFlags {} @@ -7703,6 +7712,13 @@ public class Intent implements Parcelable, Cloneable { @TestApi public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0; + /** + * This flag indicates the creator token of this intent is either missing or invalid. + * + * @hide + */ + public static final int EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN = 1 << 1; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // toUri() and parseUri() options. @@ -7870,6 +7886,7 @@ public class Intent implements Parcelable, Cloneable { this.mPackage = o.mPackage; this.mComponent = o.mComponent; this.mOriginalIntent = o.mOriginalIntent; + this.mCreatorTokenInfo = o.mCreatorTokenInfo; if (o.mCategories != null) { this.mCategories = new ArraySet<>(o.mCategories); @@ -12176,6 +12193,60 @@ public class Intent implements Parcelable, Cloneable { return (mExtras != null) ? mExtras.describeContents() : 0; } + private static class CreatorTokenInfo { + // Stores a creator token for an intent embedded as an extra intent in a top level intent, + private IBinder mCreatorToken; + // Stores all extra keys whose values are intents for a top level intent. + private ArraySet mExtraIntentKeys; + } + + private @Nullable CreatorTokenInfo mCreatorTokenInfo; + + /** @hide */ + public void removeCreatorTokenInfo() { + mCreatorTokenInfo = null; + } + + /** @hide */ + public @Nullable IBinder getCreatorToken() { + return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mCreatorToken; + } + + /** @hide */ + public Set getExtraIntentKeys() { + return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys; + } + + /** @hide */ + public void setCreatorToken(@NonNull IBinder creatorToken) { + if (mCreatorTokenInfo == null) { + mCreatorTokenInfo = new CreatorTokenInfo(); + } + mCreatorTokenInfo.mCreatorToken = creatorToken; + } + + /** + * Collects keys in the extra bundle whose value are intents. + * @hide + */ + public void collectExtraIntentKeys() { + if (!preventIntentRedirect()) return; + + if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) { + for (String key : mExtras.keySet()) { + if (mExtras.get(key) instanceof Intent) { + if (mCreatorTokenInfo == null) { + mCreatorTokenInfo = new CreatorTokenInfo(); + } + if (mCreatorTokenInfo.mExtraIntentKeys == null) { + mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>(); + } + mCreatorTokenInfo.mExtraIntentKeys.add(key); + } + } + } + } + public void writeToParcel(Parcel out, int flags) { out.writeString8(mAction); Uri.writeToParcel(out, mData); @@ -12225,6 +12296,16 @@ public class Intent implements Parcelable, Cloneable { } else { out.writeInt(0); } + + if (preventIntentRedirect()) { + if (mCreatorTokenInfo == null) { + out.writeInt(0); + } else { + out.writeInt(1); + out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken); + out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys); + } + } } public static final @android.annotation.NonNull Parcelable.Creator CREATOR @@ -12282,6 +12363,14 @@ public class Intent implements Parcelable, Cloneable { if (in.readInt() != 0) { mOriginalIntent = new Intent(in); } + + if (preventIntentRedirect()) { + if (in.readInt() != 0) { + mCreatorTokenInfo = new CreatorTokenInfo(); + mCreatorTokenInfo.mCreatorToken = in.readStrongBinder(); + mCreatorTokenInfo.mExtraIntentKeys = (ArraySet) in.readArraySet(null); + } + } } /** diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index 5acebf54a1591f9a0e4fe81a4bcc75f16905390d..d77b2f53fc5be1502b69f482e9833bef18f65903 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -94,7 +94,7 @@ public final class SharedLibraryInfo implements Parcelable { private final String mPath; private final String mPackageName; private final String mName; - private final List mCodePaths; + private List mCodePaths; private final long mVersion; private final @Type int mType; @@ -281,6 +281,15 @@ public final class SharedLibraryInfo implements Parcelable { } } + /** + * Sets new all code paths for that library. + * + * @hide + */ + public void setAllCodePaths(List paths) { + mCodePaths = paths; + } + /** * Add a library dependency to that library. Note that this * should be called under the package manager lock. diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 139ff65b4d86130d3ab81b803162a6d9fc83ef49..160cbdffe5bbab13a848f045302580dc3b0cde1a 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -293,4 +293,19 @@ flag { description: "Feature flag to provide the new methods within launcher apps class to get packages." bug: "363324203" is_fixed_read_only: true -} \ No newline at end of file +} + +flag { + name: "remove_cross_user_permission_hack" + namespace: "package_manager_service" + description: "Feature flag to remove hack code of using PackageManager.MATCH_ANY_USER flag without cross user permission." + bug: "332664521" + is_fixed_read_only: true +} + +flag { + name: "delete_packages_silently_backport" + namespace: "package_manager_service" + description: "Feature flag to enable the holder of SYSTEM_APP_PROTECTION_SERVICE role to silently delete packages. To be deprecated by delete_packages_silently." + bug: "361776825" +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 34f3b61a8a88d17c13993701d65b47c5d9b5e3be..fd1a89692da255d0b660f94e0306710fcdd4a31b 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -220,6 +220,17 @@ flag { } } +flag { + name: "cache_user_properties_correctly_read_only" + namespace: "multiuser" + description: "UserProperties cache needs to take into account who the callingUid is." + bug: "369198539" + metadata { + purpose: PURPOSE_BUGFIX + } + is_fixed_read_only: true +} + flag { name: "cache_user_serial_number" namespace: "multiuser" diff --git a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java index a2690728d9bf798c5ae0e7b0819d529ef8b29274..bb4a0e064ca9dfade55143523e386572606eebd2 100644 --- a/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java +++ b/core/java/android/database/sqlite/SQLiteCompatibilityWalFlags.java @@ -104,6 +104,11 @@ public class SQLiteCompatibilityWalFlags { sCallingGlobalSettings = true; flags = Settings.Global.getString(app.getContentResolver(), Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS); + } catch (Exception e) { + // The process is unable to read the flags. Treat this condition the same as if + // the ActivityThread application was not available. + Log.w(TAG, "Cannot read global setting " + + Settings.Global.SQLITE_COMPATIBILITY_WAL_FLAGS + " - " + e.toString()); } finally { sCallingGlobalSettings = false; } diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 21627920f598013e067ba35c32ab316fdcb53664..1b21bdf7ba45d1a43d19f1db6fcfdfde7ee2624f 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -181,7 +181,7 @@ public final class CameraManager { * @hide */ @TestApi - @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public static final int ROTATION_OVERRIDE_NONE = ICameraService.ROTATION_OVERRIDE_NONE; /** @@ -191,7 +191,7 @@ public final class CameraManager { * @hide */ @TestApi - @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT; @@ -201,7 +201,7 @@ public final class CameraManager { * @hide */ @TestApi - @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public static final int ROTATION_OVERRIDE_ROTATION_ONLY = ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY; @@ -1562,7 +1562,7 @@ public final class CameraManager { */ public static int getRotationOverride(@Nullable Context context, @Nullable PackageManager packageManager, @Nullable String packageName) { - if (com.android.window.flags.Flags.cameraCompatForFreeform()) { + if (com.android.window.flags.Flags.enableCameraCompatForDesktopWindowing()) { return getRotationOverrideInternal(context, packageManager, packageName); } else { return shouldOverrideToPortrait(packageManager, packageName) @@ -1574,7 +1574,7 @@ public final class CameraManager { /** * @hide */ - @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @TestApi public static int getRotationOverrideInternal(@Nullable Context context, @Nullable PackageManager packageManager, @Nullable String packageName) { diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index a2d24f643bfb506a8b696f272dae0a4f79908167..73b5d947c0fe3201e6c4dc38ee1560e72b507ee1 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -431,6 +431,11 @@ public abstract class DisplayManagerInternal { */ public abstract IntArray getDisplayGroupIds(); + /** + * Get all available display ids. + */ + public abstract IntArray getDisplayIds(); + /** * Called upon presentation started/ended on the display. * @param displayId the id of the display where presentation started. diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java index 12471681f913faa620458c496aaa8c2232c9ac28..51c5f4c398a1299b7f499356ad314ec9d7f58246 100644 --- a/core/java/android/hardware/face/FaceSensorConfigurations.java +++ b/core/java/android/hardware/face/FaceSensorConfigurations.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.face.IFace; import android.hardware.biometrics.face.SensorProps; +import android.hardware.biometrics.face.virtualhal.IVirtualHal; import android.os.Binder; import android.os.Parcel; import android.os.Parcelable; @@ -160,6 +161,41 @@ public class FaceSensorConfigurations implements Parcelable { dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0)); dest.writeMap(mSensorPropsMap); } + /** + * Remap fqName of VHAL because the `virtual` instance is registered + * with IVirtulalHal now (IFace previously) + * @param fqName fqName to be translated + * @return real fqName + */ + public static String remapFqName(String fqName) { + if (!fqName.contains(IFace.DESCRIPTOR + "/virtual")) { + return fqName; //no remap needed for real hardware HAL + } else { + //new Vhal instance name + return fqName.replace("IFace", "virtualhal.IVirtualHal"); + } + } + /** + * @param fqName aidl interface instance name + * @return aidl interface + */ + public static IFace getIFace(String fqName) { + if (fqName.contains("virtual")) { + String fqNameMapped = remapFqName(fqName); + Slog.i(TAG, "getIFace fqName is mapped: " + fqName + "->" + fqNameMapped); + try { + IVirtualHal vhal = IVirtualHal.Stub.asInterface( + Binder.allowBlocking(ServiceManager.waitForService(fqNameMapped))); + return vhal.getFaceHal(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in vhal.getFaceHal() call" + fqNameMapped); + } + } + + return IFace.Stub.asInterface( + Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))); + } + /** * Returns face sensor props for the HAL {@param instance}. @@ -173,14 +209,13 @@ public class FaceSensorConfigurations implements Parcelable { return props; } - final String fqName = IFace.DESCRIPTOR + "/" + instance; - IFace face = IFace.Stub.asInterface(Binder.allowBlocking( - ServiceManager.waitForDeclaredService(fqName))); try { - if (face != null) { - props = face.getSensorProps(); + final String fqName = IFace.DESCRIPTOR + "/" + instance; + final IFace fp = getIFace(fqName); + if (fp != null) { + props = fp.getSensorProps(); } else { - Slog.e(TAG, "Unable to get declared service: " + fqName); + Log.d(TAG, "IFace null for instance " + instance); } } catch (RemoteException e) { Log.d(TAG, "Unable to get sensor properties!"); diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java index bdbec5596adeeece5caf93123c3f943dd4b32c26..5ee61bcd436a8b593919b80d2957c457ab686604 100644 --- a/core/java/android/hardware/input/KeyGestureEvent.java +++ b/core/java/android/hardware/input/KeyGestureEvent.java @@ -48,49 +48,57 @@ public final class KeyGestureEvent { public static final int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL = 8; public static final int KEY_GESTURE_TYPE_TOGGLE_TASKBAR = 9; public static final int KEY_GESTURE_TYPE_TAKE_SCREENSHOT = 10; - public static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER = 11; - public static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP = 12; - public static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN = 13; - public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP = 14; - public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN = 15; - public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE = 16; - public static final int KEY_GESTURE_TYPE_VOLUME_UP = 17; - public static final int KEY_GESTURE_TYPE_VOLUME_DOWN = 18; - public static final int KEY_GESTURE_TYPE_VOLUME_MUTE = 19; - public static final int KEY_GESTURE_TYPE_ALL_APPS = 20; - public static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH = 21; - public static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH = 22; - public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS = 23; - public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK = 24; - public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE = 25; - public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT = 26; - public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT = 27; - public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT = 28; - public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT = 29; - public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 30; - public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 31; - public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 32; - public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 33; - public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 34; - public static final int KEY_GESTURE_TYPE_SLEEP = 35; - public static final int KEY_GESTURE_TYPE_WAKEUP = 36; - public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 37; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 38; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 39; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 40; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 41; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 42; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 43; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 44; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 45; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 46; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 47; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 48; - public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 49; - public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 50; - public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 51; - public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 52; - public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 53; + public static final int KEY_GESTURE_TYPE_SCREENSHOT_CHORD = 11; + public static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER = 12; + public static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP = 13; + public static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN = 14; + public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP = 15; + public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN = 16; + public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE = 17; + public static final int KEY_GESTURE_TYPE_VOLUME_UP = 18; + public static final int KEY_GESTURE_TYPE_VOLUME_DOWN = 19; + public static final int KEY_GESTURE_TYPE_VOLUME_MUTE = 20; + public static final int KEY_GESTURE_TYPE_ALL_APPS = 21; + public static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH = 22; + public static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH = 23; + public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS = 24; + public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK = 25; + public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE = 26; + public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT = 27; + public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT = 28; + public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT = 29; + public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT = 30; + public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT = 31; + public static final int KEY_GESTURE_TYPE_LOCK_SCREEN = 32; + public static final int KEY_GESTURE_TYPE_OPEN_NOTES = 33; + public static final int KEY_GESTURE_TYPE_TOGGLE_POWER = 34; + public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION = 35; + public static final int KEY_GESTURE_TYPE_SLEEP = 36; + public static final int KEY_GESTURE_TYPE_WAKEUP = 37; + public static final int KEY_GESTURE_TYPE_MEDIA_KEY = 38; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER = 39; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL = 40; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS = 41; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR = 42; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR = 43; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC = 44; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS = 45; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING = 46; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY = 47; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES = 48; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER = 49; + public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS = 50; + public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME = 51; + public static final int KEY_GESTURE_TYPE_DESKTOP_MODE = 52; + public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION = 53; + public static final int KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER = 54; + public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD = 55; + public static final int KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD = 56; + public static final int KEY_GESTURE_TYPE_GLOBAL_ACTIONS = 57; + public static final int KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD = 58; + public static final int KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT = 59; + public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT = 60; + public static final int KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS = 61; public static final int FLAG_CANCELLED = 1; @@ -116,6 +124,7 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KEY_GESTURE_TYPE_TOGGLE_TASKBAR, KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + KEY_GESTURE_TYPE_SCREENSHOT_CHORD, KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, KEY_GESTURE_TYPE_BRIGHTNESS_UP, KEY_GESTURE_TYPE_BRIGHTNESS_DOWN, @@ -158,7 +167,15 @@ public final class KeyGestureEvent { KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME, KEY_GESTURE_TYPE_DESKTOP_MODE, KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION, - KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER + KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER, + KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, + KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD, + KEY_GESTURE_TYPE_GLOBAL_ACTIONS, + KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT, + KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, + KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, + }) @Retention(RetentionPolicy.SOURCE) public @interface KeyGestureType { @@ -360,6 +377,7 @@ public final class KeyGestureEvent { case KEY_GESTURE_TYPE_TOGGLE_TASKBAR: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR; case KEY_GESTURE_TYPE_TAKE_SCREENSHOT: + case KEY_GESTURE_TYPE_SCREENSHOT_CHORD: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT; case KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER; @@ -472,6 +490,8 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_TOGGLE_TASKBAR"; case KEY_GESTURE_TYPE_TAKE_SCREENSHOT: return "KEY_GESTURE_TYPE_TAKE_SCREENSHOT"; + case KEY_GESTURE_TYPE_SCREENSHOT_CHORD: + return "KEY_GESTURE_TYPE_SCREENSHOT_CHORD"; case KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER: return "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER"; case KEY_GESTURE_TYPE_BRIGHTNESS_UP: @@ -558,6 +578,20 @@ public final class KeyGestureEvent { return "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION"; case KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER: return "KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER"; + case KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD: + return "KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD"; + case KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD: + return "KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD"; + case KEY_GESTURE_TYPE_GLOBAL_ACTIONS: + return "KEY_GESTURE_TYPE_GLOBAL_ACTIONS"; + case KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD: + return "KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD"; + case KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT: + return "KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT"; + case KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT: + return "KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT"; + case KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS: + return "KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS"; default: return Integer.toHexString(value); } diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 5c07fa48d46626da881cfe6917cab24aa9ce2107..22ae67672950b5889295197398a63a5bf67588b7 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -155,16 +155,16 @@ public class ConversionUtil { public static RecognitionConfig api2aidlRecognitionConfig( SoundTrigger.RecognitionConfig apiConfig) { RecognitionConfig aidlConfig = new RecognitionConfig(); - aidlConfig.captureRequested = apiConfig.captureRequested; - // apiConfig.allowMultipleTriggers is ignored by the lower layers. + aidlConfig.captureRequested = apiConfig.isCaptureRequested(); + // apiConfig.isAllowMultipleTriggers() is ignored by the lower layers. aidlConfig.phraseRecognitionExtras = - new PhraseRecognitionExtra[apiConfig.keyphrases.length]; - for (int i = 0; i < apiConfig.keyphrases.length; ++i) { + new PhraseRecognitionExtra[apiConfig.getKeyphrases().size()]; + for (int i = 0; i < apiConfig.getKeyphrases().size(); ++i) { aidlConfig.phraseRecognitionExtras[i] = api2aidlPhraseRecognitionExtra( - apiConfig.keyphrases[i]); + apiConfig.getKeyphrases().get(i)); } - aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length); - aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities); + aidlConfig.data = Arrays.copyOf(apiConfig.getData(), apiConfig.getData().length); + aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.getAudioCapabilities()); return aidlConfig; } diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 9f3e3ad8c01e869866eaf9e800a41b40de1b4096..05e91e447a43882389d85ebaa48c34806a0cf6ac 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -63,6 +63,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; @@ -1513,50 +1514,60 @@ public class SoundTrigger { * A RecognitionConfig is provided to * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} to configure the * recognition request. - * - * @hide */ - @TestApi + @FlaggedApi(android.media.soundtrigger.Flags.FLAG_MANAGER_API) public static final class RecognitionConfig implements Parcelable { - /** True if the DSP should capture the trigger sound and make it available for further - * capture. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public final boolean captureRequested; - /** - * True if the service should restart listening after the DSP triggers. - * Note: This config flag is currently used at the service layer rather than by the DSP. - */ - public final boolean allowMultipleTriggers; - /** List of all keyphrases in the sound model for which recognition should be performed with - * options for each keyphrase. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @NonNull - @SuppressLint("ArrayReturn") - public final KeyphraseRecognitionExtra keyphrases[]; - /** Opaque data for use by system applications who know about voice engine internals, - * typically during enrollment. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @NonNull - public final byte[] data; + private final boolean mCaptureRequested; + private final boolean mAllowMultipleTriggers; + private final KeyphraseRecognitionExtra mKeyphrases[]; + private final byte[] mData; + @ModuleProperties.AudioCapabilities + private final int mAudioCapabilities; /** - * Bit field encoding of the AudioCapabilities - * supported by the firmware. + * Constructor for {@link RecognitionConfig} with {@code audioCapabilities} describes a + * config that can be used by + * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)} + * + * @deprecated should use builder-based constructor instead. + * TODO(b/368042125): remove this method. + * @param captureRequested Whether the DSP should capture the trigger sound. + * @param allowMultipleTriggers Whether the service should restart listening after the DSP + * triggers. + * @param keyphrases List of keyphrases in the sound model. + * @param data Opaque data for use by system applications who know about voice engine + * internals, typically during enrollment. + * @param audioCapabilities Bit field encoding of the AudioCapabilities. + * + * @hide */ - @ModuleProperties.AudioCapabilities - public final int audioCapabilities; - + @Deprecated + @SuppressWarnings("Todo") + @TestApi public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, int audioCapabilities) { - this.captureRequested = captureRequested; - this.allowMultipleTriggers = allowMultipleTriggers; - this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; - this.data = data != null ? data : new byte[0]; - this.audioCapabilities = audioCapabilities; + this.mCaptureRequested = captureRequested; + this.mAllowMultipleTriggers = allowMultipleTriggers; + this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; + this.mData = data != null ? data : new byte[0]; + this.mAudioCapabilities = audioCapabilities; } + /** + * Constructor for {@link RecognitionConfig} without audioCapabilities. The + * audioCapabilities is set to 0. + * + * @param captureRequested Whether the DSP should capture the trigger sound. + * @param allowMultipleTriggers Whether the service should restart listening after the DSP + * triggers. + * @param keyphrases List of keyphrases in the sound model. + * @param data Opaque data for use by system applications. + * + * @hide + */ @UnsupportedAppUsage + @TestApi public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { @@ -1574,9 +1585,52 @@ public class SoundTrigger { } }; + /** + * Returns whether the DSP should capture the trigger sound and make it available for + * further capture. + */ + public boolean isCaptureRequested() { + return mCaptureRequested; + } + + /** + * Returns whether the service should restart listening after the DSP triggers. + * + *

          Note: This config flag is currently used at the service layer rather than by + * the DSP. + */ + public boolean isAllowMultipleTriggers() { + return mAllowMultipleTriggers; + } + + /** + * Gets all keyphrases in the sound model for which recognition should be performed with + * options for each keyphrase. + */ + @NonNull + public List getKeyphrases() { + return Arrays.asList(mKeyphrases); + } + + /** + * Opaque data. + * + *

          For use by system applications who knows about voice engine internals, typically + * during enrollment. + */ + @NonNull + public byte[] getData() { + return mData; + } + + /** Bit field encoding of the AudioCapabilities supported by the firmware. */ + public int getAudioCapabilities() { + return mAudioCapabilities; + } + private static RecognitionConfig fromParcel(Parcel in) { - boolean captureRequested = in.readByte() == 1; - boolean allowMultipleTriggers = in.readByte() == 1; + boolean captureRequested = in.readBoolean(); + boolean allowMultipleTriggers = in.readBoolean(); KeyphraseRecognitionExtra[] keyphrases = in.createTypedArray(KeyphraseRecognitionExtra.CREATOR); byte[] data = in.readBlob(); @@ -1587,11 +1641,11 @@ public class SoundTrigger { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeByte((byte) (captureRequested ? 1 : 0)); - dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); - dest.writeTypedArray(keyphrases, flags); - dest.writeBlob(data); - dest.writeInt(audioCapabilities); + dest.writeBoolean(mCaptureRequested); + dest.writeBoolean(mAllowMultipleTriggers); + dest.writeTypedArray(mKeyphrases, flags); + dest.writeBlob(mData); + dest.writeInt(mAudioCapabilities); } @Override @@ -1601,10 +1655,10 @@ public class SoundTrigger { @Override public String toString() { - return "RecognitionConfig [captureRequested=" + captureRequested - + ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases=" - + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) - + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]"; + return "RecognitionConfig [captureRequested=" + mCaptureRequested + + ", allowMultipleTriggers=" + mAllowMultipleTriggers + ", keyphrases=" + + Arrays.toString(mKeyphrases) + ", data=" + Arrays.toString(mData) + + ", audioCapabilities=" + Integer.toHexString(mAudioCapabilities) + "]"; } @Override @@ -1616,19 +1670,19 @@ public class SoundTrigger { if (!(obj instanceof RecognitionConfig)) return false; RecognitionConfig other = (RecognitionConfig) obj; - if (captureRequested != other.captureRequested) { + if (mCaptureRequested != other.mCaptureRequested) { return false; } - if (allowMultipleTriggers != other.allowMultipleTriggers) { + if (mAllowMultipleTriggers != other.mAllowMultipleTriggers) { return false; } - if (!Arrays.equals(keyphrases, other.keyphrases)) { + if (!Arrays.equals(mKeyphrases, other.mKeyphrases)) { return false; } - if (!Arrays.equals(data, other.data)) { + if (!Arrays.equals(mData, other.mData)) { return false; } - if (audioCapabilities != other.audioCapabilities) { + if (mAudioCapabilities != other.mAudioCapabilities) { return false; } return true; @@ -1638,13 +1692,96 @@ public class SoundTrigger { public final int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (captureRequested ? 1 : 0); - result = prime * result + (allowMultipleTriggers ? 1 : 0); - result = prime * result + Arrays.hashCode(keyphrases); - result = prime * result + Arrays.hashCode(data); - result = prime * result + audioCapabilities; + result = prime * result + (mCaptureRequested ? 1 : 0); + result = prime * result + (mAllowMultipleTriggers ? 1 : 0); + result = prime * result + Arrays.hashCode(mKeyphrases); + result = prime * result + Arrays.hashCode(mData); + result = prime * result + mAudioCapabilities; return result; } + + /** + * Builder class for {@link RecognitionConfig} objects. + */ + public static final class Builder { + private boolean mCaptureRequested; + private boolean mAllowMultipleTriggers; + @Nullable private KeyphraseRecognitionExtra[] mKeyphrases; + @Nullable private byte[] mData; + private int mAudioCapabilities; + + /** + * Constructs a new Builder with the default values. + */ + public Builder() { + } + + /** + * Sets capture requested state. + * @param captureRequested The new requested state. + * @return the same Builder instance. + */ + public @NonNull Builder setCaptureRequested(boolean captureRequested) { + mCaptureRequested = captureRequested; + return this; + } + + /** + * Sets allow multiple triggers state. + * @param allowMultipleTriggers The new allow multiple triggers state. + * @return the same Builder instance. + */ + public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) { + mAllowMultipleTriggers = allowMultipleTriggers; + return this; + } + + /** + * Sets the keyphrases field. + * @param keyphrases The new keyphrases. + * @return the same Builder instance. + */ + public @NonNull Builder setKeyphrases( + @NonNull Collection keyphrases) { + mKeyphrases = keyphrases.toArray(new KeyphraseRecognitionExtra[keyphrases.size()]); + return this; + } + + /** + * Sets the data field. + * @param data The new data. + * @return the same Builder instance. + */ + public @NonNull Builder setData(@Nullable byte[] data) { + mData = data; + return this; + } + + /** + * Sets the audio capabilities field. + * @param audioCapabilities The new audio capabilities. + * @return the same Builder instance. + */ + public @NonNull Builder setAudioCapabilities(int audioCapabilities) { + mAudioCapabilities = audioCapabilities; + return this; + } + + /** + * Combines all of the parameters that have been set and return a new + * {@link RecognitionConfig} object. + * @return a new {@link RecognitionConfig} object + */ + public @NonNull RecognitionConfig build() { + RecognitionConfig config = new RecognitionConfig( + /* captureRequested= */ mCaptureRequested, + /* allowMultipleTriggers= */ mAllowMultipleTriggers, + /* keyphrases= */ mKeyphrases, + /* data= */ mData, + /* audioCapabilities= */ mAudioCapabilities); + return config; + } + }; } /** diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 91cdf8d8fcae04139f08ba21be7e29665aa1c4f7..1c9be6fb4b826edef1e130af66c3d6f763c59105 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -76,11 +76,15 @@ import java.util.concurrent.Executor; * PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} before querying the service. If the feature is * absent, {@link Context#getSystemService} may return null. */ -@SystemService(Context.VCN_MANAGEMENT_SERVICE) +@SystemService(VcnManager.VCN_MANAGEMENT_SERVICE_STRING) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) public class VcnManager { @NonNull private static final String TAG = VcnManager.class.getSimpleName(); + // TODO: b/366598445: Expose and use Context.VCN_MANAGEMENT_SERVICE + /** @hide */ + public static final String VCN_MANAGEMENT_SERVICE_STRING = "vcn_management"; + /** * Key for WiFi entry RSSI thresholds * diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java index 1fef602b8dd7f8bb5bf8daf08711ee23dfe0ed45..a698b9d9721549b610aebf0ca146575d6c847d84 100644 --- a/core/java/android/os/BatteryUsageStats.java +++ b/core/java/android/os/BatteryUsageStats.java @@ -788,6 +788,12 @@ public final class BatteryUsageStats implements Parcelable, Closeable { /** Parses an XML representation of BatteryUsageStats */ public static BatteryUsageStats createFromXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException { + return createBuilderFromXml(parser).build(); + } + + /** Parses an XML representation of BatteryUsageStats */ + public static BatteryUsageStats.Builder createBuilderFromXml(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { Builder builder = null; int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { @@ -862,7 +868,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable { eventType = parser.next(); } - return builder.build(); + return builder; } @Override @@ -978,9 +984,20 @@ public final class BatteryUsageStats implements Parcelable, Closeable { */ @NonNull public BatteryUsageStats build() { + if (mBatteryConsumersCursorWindow == null) { + throw new IllegalStateException("Builder has been discarded"); + } return new BatteryUsageStats(this); } + /** + * Close this builder without actually calling ".build()". Do not attempt + * to continue using the builder after this call. + */ + public void discard() { + mBatteryConsumersCursorWindow.close(); + } + /** * Sets the battery capacity in milli-amp-hours. */ diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java index a12606bc2e66e0d698c4f8163b31755dd4393518..b533225192ba7939b273f01384293e727c988b2c 100644 --- a/core/java/android/os/BatteryUsageStatsQuery.java +++ b/core/java/android/os/BatteryUsageStatsQuery.java @@ -77,6 +77,8 @@ public final class BatteryUsageStatsQuery implements Parcelable { public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE = 0x0040; + public static final int FLAG_BATTERY_USAGE_STATS_ACCUMULATED = 0x0080; + private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000; private final int mFlags; @@ -327,6 +329,15 @@ public final class BatteryUsageStatsQuery implements Parcelable { return this; } + /** + * Requests the full continuously accumulated battery usage stats: across reboots + * and most battery stats resets. + */ + public Builder accumulated() { + mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED; + return this; + } + /** * Requests to aggregate stored snapshots between the two supplied timestamps * @param fromTimestamp Exclusive starting timestamp, as per System.currentTimeMillis() diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 97e9f34064ba92162c09631df440eac22c63807d..ed75491b8e21259023e8a96e3cf09cca13b3129a 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -1110,6 +1110,21 @@ public class Binder implements IBinder { @Nullable String[] args) { } + /** + * Called whenever the stub implementation throws an exception which isn't propagated to the + * remote caller by the binder. If this method isn't overridden, this exception is swallowed, + * and some default return values are propagated to the caller. + * + *
          This should not throw. Doing so would defeat the purpose of this handler, and + * suppress the exception it is handling. + * + * @param code The transaction code being handled + * @param e The exception which was thrown. + * @hide + */ + protected void onUnhandledException(int code, int flags, Exception e) { + } + /** * @param in The raw file descriptor that an input data stream can be read from. * @param out The raw file descriptor that normal command messages should be written to. @@ -1408,10 +1423,15 @@ public class Binder implements IBinder { } else { Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e); } + onUnhandledException(code, flags, e); } else { // Clear the parcel before writing the exception. reply.setDataSize(0); reply.setDataPosition(0); + // The writeException below won't do anything useful if this is the case. + if (Parcel.getExceptionCode(e) == 0) { + onUnhandledException(code, flags, e); + } reply.writeException(e); } res = true; diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index dbb6f92c64119064510d2dee713f2ac1aea25f41..5f62b8be45a30a90f62b458ae3955e60bd5b7970 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -46,6 +46,7 @@ interface IPowerManager void userActivity(int displayId, long time, int event, int flags); void wakeUp(long time, int reason, String details, String opPackageName); + void wakeUpWithDisplayId(long time, int reason, String details, String opPackageName, int displayId); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void goToSleep(long time, int reason, int flags); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java index 0776cf405cfeb88dfaf9657dc6355599f848a4b3..e2a72dd5e385d76d82d1e4d34e64507c074ea9e1 100644 --- a/core/java/android/os/IpcDataCache.java +++ b/core/java/android/os/IpcDataCache.java @@ -48,6 +48,20 @@ import java.util.concurrent.atomic.AtomicLong; * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing, * but doesn't hold a lock across data fetches on query misses. * + * Clients should be aware of the following commonly-seen issues: + *

            + * + *
          • Client calls will not go through the cache before the first invalidation signal is + * received. Therefore, servers should signal an invalidation as soon as they have data to offer to + * clients. + * + *
          • Cache invalidation is restricted to well-known processes, which means that test code cannot + * invalidate a cache. {@link #disableForTestMode()} and {@link #testPropertyName} must be used in + * test processes that attempt cache invalidation. See + * {@link PropertyInvalidatedCacheTest#testBasicCache()} for an example. + * + *
          + * * The intended use case is caching frequently-read, seldom-changed information normally retrieved * across interprocess communication. Imagine that you've written a user birthday information * daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface over @@ -136,20 +150,20 @@ import java.util.concurrent.atomic.AtomicLong; * string is permitted. The third parameters is the name of the API being cached; this, too, can * any value. The fourth is the name of the cache. The cache is usually named after th API. * Some things you must know about the three strings: - * - *
            The system property that controls the cache is named {@code cache_key..}. + *
              + *
            • The system property that controls the cache is named {@code cache_key..}. * Usually, the SELinux rules permit a process to write a system property (and therefore * invalidate a cache) based on the wildcard {@code cache_key..*}. This means that * although the cache can be constructed with any module string, whatever string is chosen must be * consistent with the SELinux configuration. - *
                The API name can be any string of alphanumeric characters. All caches with the same API + *
              • The API name can be any string of alphanumeric characters. All caches with the same API * are invalidated at the same time. If a server supports several caches and all are invalidated * in common, then it is most efficient to assign the same API string to every cache. - *
                  The cache name can be any string. In debug output, the name is used to distiguish between + *
                • The cache name can be any string. In debug output, the name is used to distiguish between * caches with the same API name. The cache name is also used when disabling caches in the * current process. So, invalidation is based on the module+api but disabling (which is generally * a once-per-process operation) is based on the cache name. - * + *
                * * User birthdays do occasionally change, so we have to modify the server to invalidate this * cache when necessary. That invalidation code looks like this: diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index e4c12b6ff834926273710b512b0e738ab4afa46d..b9bae5b9f73781c5bf4e0eafa34cc78f3a77f361 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -1567,27 +1567,9 @@ public final class PowerManager { } /** - * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} - * to turn on. - * - *

                If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is - * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If - * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already - * on then nothing will happen. - * - *

                - * This is what happens when the power key is pressed to turn on the screen. - *

                - * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. - *

                - * - * @param time The time when the request to wake up was issued, in the - * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly - * order the wake up request with other power management functions. It should be set - * to the timestamp of the input event that caused the request to wake up. + * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on. * - * @see #userActivity - * @see #goToSleep + * @see #wakeUp(long, int, String, int) * * @deprecated Use {@link #wakeUp(long, int, String)} instead. * @removed Requires signature permission. @@ -1598,30 +1580,9 @@ public final class PowerManager { } /** - * Forces the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} - * to turn on. - * - *

                If the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is - * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If - * the {@link android.view.Display#DEFAULT_DISPLAY_GROUP default display group} is already - * on then nothing will happen. - * - *

                - * This is what happens when the power key is pressed to turn on the screen. - *

                - * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission. - *

                - * - * @param time The time when the request to wake up was issued, in the - * {@link SystemClock#uptimeMillis()} time base. This timestamp is used to correctly - * order the wake up request with other power management functions. It should be set - * to the timestamp of the input event that caused the request to wake up. - * - * @param details A free form string to explain the specific details behind the wake up for - * debugging purposes. + * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on. * - * @see #userActivity - * @see #goToSleep + * @see #wakeUp(long, int, String, int) * * @deprecated Use {@link #wakeUp(long, int, String)} instead. * @hide @@ -1635,9 +1596,23 @@ public final class PowerManager { /** * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on. * - *

                If the {@link android.view.Display#DEFAULT_DISPLAY default display} is turned off it will - * be turned on. Additionally, if the device is asleep it will be awoken. If the {@link - * android.view.Display#DEFAULT_DISPLAY default display} is already on then nothing will happen. + * @see #wakeUp(long, int, String, int) + * @hide + */ + public void wakeUp(long time, @WakeReason int reason, String details) { + try { + mService.wakeUp(time, reason, details, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Forces the display with the supplied displayId to turn on. + * + *

                If the corresponding display is turned off, it will be turned on. Additionally, if the + * device is asleep it will be awoken. If the corresponding display is already on then nothing + * will happen. If the corresponding display does not exist, then nothing will happen. * *

                If the device is an Android TV playback device, it will attempt to turn on the * HDMI-connected TV and become the current active source via the HDMI-CEC One Touch Play @@ -1658,14 +1633,16 @@ public final class PowerManager { * * @param details A free form string to explain the specific details behind the wake up for * debugging purposes. + * @param displayId The displayId of the display to be woken up. * * @see #userActivity * @see #goToSleep * @hide */ - public void wakeUp(long time, @WakeReason int reason, String details) { + public void wakeUp(long time, @WakeReason int reason, String details, int displayId) { try { - mService.wakeUp(time, reason, details, mContext.getOpPackageName()); + mService.wakeUpWithDisplayId(time, reason, details, mContext.getOpPackageName(), + displayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index 4c9a02c1fc49d4fa1d3db3e8dc387b95067b3b85..dfc591b322df13b6e81df3a2b19abe1c5b6aa8a1 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -28,6 +28,8 @@ import android.location.LocationTime; import android.text.format.DateUtils; import android.util.Slog; +import com.android.internal.os.ApplicationSharedMemory; + import dalvik.annotation.optimization.CriticalNative; import java.time.Clock; @@ -323,74 +325,74 @@ public final class SystemClock { } /** - * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized - * using a remote network source outside the device. - *

                - * While the time returned by {@link System#currentTimeMillis()} can be - * adjusted by the user, the time returned by this method cannot be adjusted - * by the user. - *

                - * This performs no blocking network operations and returns values based on - * a recent successful synchronization event; it will either return a valid - * time or throw. - *

                - * Note that synchronization may occur using an insecure network protocol, - * so the returned time should not be used for security purposes. - * The device may resynchronize with the same or different network source - * at any time. Due to network delays, variations between servers, or local - * (client side) clock drift, the accuracy of the returned times cannot be - * guaranteed. In extreme cases, consecutive calls to {@link - * #currentNetworkTimeMillis(ITimeDetectorService)} could return times that - * are out of order. + * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized using a remote + * network source outside the device. + * + *

                While the time returned by {@link System#currentTimeMillis()} can be adjusted by the user, + * the time returned by this method cannot be adjusted by the user. + * + *

                This performs no blocking network operations and returns values based on a recent + * successful synchronization event; it will either return a valid time or throw. + * + *

                Note that synchronization may occur using an insecure network protocol, so the returned + * time should not be used for security purposes. The device may resynchronize with the same or + * different network source at any time. Due to network delays, variations between servers, or + * local (client side) clock drift, the accuracy of the returned times cannot be guaranteed. In + * extreme cases, consecutive calls to {@link #currentNetworkTimeMillis()} could return times + * that are out of order. * * @throws DateTimeException when no network time can be provided. * @hide */ public static long currentNetworkTimeMillis() { - ITimeDetectorService timeDetectorService = getITimeDetectorService(); - if (timeDetectorService == null) { - throw new RuntimeException(new DeadSystemException()); - } + if (com.android.internal.os.Flags.applicationSharedMemoryEnabled() + && Flags.networkTimeUsesSharedMemory()) { + final long latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis = + ApplicationSharedMemory.getInstance() + .getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(); + return latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis + elapsedRealtime(); + } else { + ITimeDetectorService timeDetectorService = getITimeDetectorService(); + if (timeDetectorService == null) { + throw new RuntimeException(new DeadSystemException()); + } - UnixEpochTime time; - try { - time = timeDetectorService.latestNetworkTime(); - } catch (ParcelableException e) { - e.maybeRethrow(DateTimeException.class); - throw new RuntimeException(e); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - if (time == null) { - // This is not expected. - throw new DateTimeException("Network based time is not available."); - } + UnixEpochTime time; + try { + time = timeDetectorService.latestNetworkTime(); + } catch (ParcelableException e) { + e.maybeRethrow(DateTimeException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (time == null) { + // This is not expected. + throw new DateTimeException("Network based time is not available."); + } - long currentMillis = elapsedRealtime(); - long deltaMs = currentMillis - time.getElapsedRealtimeMillis(); - return time.getUnixEpochTimeMillis() + deltaMs; + long currentMillis = elapsedRealtime(); + long deltaMs = currentMillis - time.getElapsedRealtimeMillis(); + return time.getUnixEpochTimeMillis() + deltaMs; + } } - /** - * Returns a {@link Clock} that starts at January 1, 1970 00:00:00.0 UTC, - * synchronized using a remote network source outside the device. - *

                - * While the time returned by {@link System#currentTimeMillis()} can be - * adjusted by the user, the time returned by this method cannot be adjusted - * by the user. - *

                - * This performs no blocking network operations and returns values based on - * a recent successful synchronization event; it will either return a valid - * time or throw. - *

                - * Note that synchronization may occur using an insecure network protocol, - * so the returned time should not be used for security purposes. - * The device may resynchronize with the same or different network source - * at any time. Due to network delays, variations between servers, or local - * (client side) clock drift, the accuracy of the returned times cannot be - * guaranteed. In extreme cases, consecutive calls to {@link - * Clock#millis()} on the returned {@link Clock} could return times that are - * out of order. + /** + * Returns a {@link Clock} that starts at January 1, 1970 00:00:00.0 UTC, synchronized using a + * remote network source outside the device. + * + *

                While the time returned by {@link System#currentTimeMillis()} can be adjusted by the user, + * the time returned by this method cannot be adjusted by the user. + * + *

                This performs no blocking network operations and returns values based on a recent + * successful synchronization event; it will either return a valid time or throw. + * + *

                Note that synchronization may occur using an insecure network protocol, so the returned + * time should not be used for security purposes. The device may resynchronize with the same or + * different network source at any time. Due to network delays, variations between servers, or + * local (client side) clock drift, the accuracy of the returned times cannot be guaranteed. In + * extreme cases, consecutive calls to {@link Clock#millis()} on the returned {@link Clock} + * could return times that are out of order. * * @throws DateTimeException when no network time can be provided. */ diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index e53873b5622ecdeafe369bc6da16974cd609a74c..89b727cdd6556bf4b74bc15d41dcccdd8820aa78 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -21,13 +21,10 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.ravenwood.annotation.RavenwoodKeepWholeClass; -import android.ravenwood.annotation.RavenwoodRedirect; -import android.ravenwood.annotation.RavenwoodRedirectionClass; import android.util.Log; import android.util.MutableInt; import com.android.internal.annotations.GuardedBy; -import com.android.internal.ravenwood.RavenwoodEnvironment; import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; @@ -40,8 +37,6 @@ import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.Map; -import java.util.function.Predicate; /** * Gives access to the system properties store. The system properties @@ -58,7 +53,6 @@ import java.util.function.Predicate; */ @SystemApi @RavenwoodKeepWholeClass -@RavenwoodRedirectionClass("SystemProperties_host") public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; @@ -76,7 +70,7 @@ public class SystemProperties { @UnsupportedAppUsage @GuardedBy("sChangeCallbacks") - static final ArrayList sChangeCallbacks = new ArrayList(); + private static final ArrayList sChangeCallbacks = new ArrayList(); @GuardedBy("sRoReads") private static final HashMap sRoReads = @@ -102,19 +96,6 @@ public class SystemProperties { } } - /** @hide */ - @RavenwoodRedirect - public static void init$ravenwood(Map values, - Predicate keyReadablePredicate, Predicate keyWritablePredicate) { - throw RavenwoodEnvironment.notSupportedOnDevice(); - } - - /** @hide */ - @RavenwoodRedirect - public static void reset$ravenwood() { - throw RavenwoodEnvironment.notSupportedOnDevice(); - } - // The one-argument version of native_get used to be a regular native function. Nowadays, // we use the two-argument form of native_get all the time, but we can't just delete the // one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation @@ -126,46 +107,34 @@ public class SystemProperties { @FastNative @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - @RavenwoodRedirect private static native String native_get(String key, String def); @FastNative @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - @RavenwoodRedirect private static native int native_get_int(String key, int def); @FastNative @UnsupportedAppUsage - @RavenwoodRedirect private static native long native_get_long(String key, long def); @FastNative @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - @RavenwoodRedirect private static native boolean native_get_boolean(String key, boolean def); @FastNative - @RavenwoodRedirect private static native long native_find(String name); @FastNative - @RavenwoodRedirect private static native String native_get(long handle); @CriticalNative - @RavenwoodRedirect private static native int native_get_int(long handle, int def); @CriticalNative - @RavenwoodRedirect private static native long native_get_long(long handle, long def); @CriticalNative - @RavenwoodRedirect private static native boolean native_get_boolean(long handle, boolean def); // _NOT_ FastNative: native_set performs IPC and can block @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - @RavenwoodRedirect private static native void native_set(String key, String def); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) - @RavenwoodRedirect private static native void native_add_change_callback(); - @RavenwoodRedirect private static native void native_report_sysprop_change(); /** @@ -301,7 +270,7 @@ public class SystemProperties { } @SuppressWarnings("unused") // Called from native code. - static void callChangeCallbacks() { + private static void callChangeCallbacks() { ArrayList callbacks = null; synchronized (sChangeCallbacks) { //Log.i("foo", "Calling " + sChangeCallbacks.size() + " change callbacks!"); @@ -326,6 +295,16 @@ public class SystemProperties { } } + /** + * Clear all callback changes. + * @hide + */ + public static void clearChangeCallbacksForTest() { + synchronized (sChangeCallbacks) { + sChangeCallbacks.clear(); + } + } + /** * Notifies listeners that a system property has changed * @hide diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index cfbf5289931d940ac0bb4738764394b05869dc43..a5697fb0e8a8d6d4cedcf596639a2664702f6967 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -16,6 +16,8 @@ package android.os; +import static android.os.Trace.TRACE_TAG_VIBRATOR; + import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; @@ -138,14 +140,14 @@ public class SystemVibratorManager extends VibratorManager { Log.w(TAG, "Failed to vibrate; no vibrator manager service."); return; } - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason=" + reason); + Trace.traceBegin(TRACE_TAG_VIBRATOR, "vibrate"); try { mService.vibrate(uid, mContext.getDeviceId(), opPkg, effect, attributes, reason, mToken); } catch (RemoteException e) { Log.w(TAG, "Failed to vibrate.", e); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + Trace.traceEnd(TRACE_TAG_VIBRATOR); } } @@ -155,14 +157,14 @@ public class SystemVibratorManager extends VibratorManager { Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service."); return; } - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "performHapticFeedback, reason=" + reason); + Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedback"); try { mService.performHapticFeedback(mUid, mContext.getDeviceId(), mPackageName, constant, reason, flags, privFlags); } catch (RemoteException e) { Log.w(TAG, "Failed to perform haptic feedback.", e); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + Trace.traceEnd(TRACE_TAG_VIBRATOR); } } @@ -174,15 +176,14 @@ public class SystemVibratorManager extends VibratorManager { + " no vibrator manager service."); return; } - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, - "performHapticFeedbackForInputDevice, reason=" + reason); + Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice"); try { mService.performHapticFeedbackForInputDevice(mUid, mContext.getDeviceId(), mPackageName, constant, inputDeviceId, inputSource, reason, flags, privFlags); } catch (RemoteException e) { Log.w(TAG, "Failed to perform haptic feedback for input device.", e); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + Trace.traceEnd(TRACE_TAG_VIBRATOR); } } diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index f670601bd0d48668d3a7bb73626ce9bb955d3742..a1bfe39c0fc4df446754e3896f8dc75e50ee04cb 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -224,3 +224,11 @@ flag { is_exported: true bug: "366598445" } + +flag { + name: "network_time_uses_shared_memory" + namespace: "system_performance" + description: "SystemClock.currentNetworkTimeMillis() reads network time offset from shared memory" + bug: "361329788" + is_exported: true +} diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index b0791e36e124be72916c7b282be91766b4a06e1c..bca5bcc99c7e53d3ba4176c1dffaa6a84765eb2e 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -250,14 +250,6 @@ flag { bug: "349942654" } -flag { - name: "replace_body_sensors_permission_enabled" - is_exported: true - namespace: "android_health_services" - description: "This flag is used to enable replacing permission BODY_SENSORS(and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE(and READ_HEALTH_DATA_IN_BACKGROUND)" - bug: "364638912" -} - flag { name: "appop_access_tracking_logging_enabled" is_fixed_read_only: true @@ -265,3 +257,12 @@ flag { description: "Enables logging of the AppOp access tracking" bug: "365584286" } + +flag { + name: "replace_body_sensor_permission_enabled" + is_fixed_read_only: true + is_exported: true + namespace: "android_health_services" + description: "This fixed read-only flag is used to enable replacing permission BODY_SENSORS (and BODY_SENSORS_BACKGROUND) with granular health permission READ_HEART_RATE (and READ_HEALTH_DATA_IN_BACKGROUND)" + bug: "364638912" +} diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index a622810496788d594be15fb7a9b173d322cb736c..27b1dfbd9b1884adc1a3ad16ad435a8b95e545cf 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -3046,6 +3046,11 @@ public final class ContactsContract { *

              • {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a * cloud-synced account. New raw contacts requested for insertion without a specified * {@link Account} will be saved in the default cloud account.
              • + *
              • {@link #DEFAULT_ACCOUNT_STATE_SIM}: The default account is set to a + * account that is associated with one of + * {@link SimContacts#getSimAccounts(ContentResolver)}. New raw contacts requested + * for insertion without a specified {@link Account} will be + * saved in this SIM account.
              • *
              */ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED) @@ -3062,45 +3067,52 @@ public final class ContactsContract { */ public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; + /** + * A state indicating that the default account is set as an account that is + * associated with one of {@link SimContacts#getSimAccounts(ContentResolver)}. + */ + public static final int DEFAULT_ACCOUNT_STATE_SIM = 4; + /** * The state of the default account. One of * {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}, - * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or - * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. + * {@link #DEFAULT_ACCOUNT_STATE_LOCAL}, + * {@link #DEFAULT_ACCOUNT_STATE_CLOUD} + * {@link #DEFAULT_ACCOUNT_STATE_SIM}. */ @DefaultAccountState private final int mState; /** - * The account of the default account, when {@link mState} is { + * The account of the default account, when {@link #mState} is { * - * @link #STATE_SET_TO_CLOUD}, or null otherwise. + * @link #DEFAULT_ACCOUNT_STATE_CLOUD} or {@link #DEFAULT_ACCOUNT_STATE_SIM}, or + * null otherwise. */ - private final Account mCloudAccount; + private final Account mAccount; /** * Constructs a new `DefaultAccountAndState` instance with the specified state and * cloud * account. * - * @param state The state of the default account. - * @param cloudAccount The cloud account associated with the default account, - * or null if the state is not - * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. + * @param state The state of the default account. + * @param account The account associated with the default account if the state is + * {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or + * {@link #DEFAULT_ACCOUNT_STATE_SIM}, or null otherwise. */ public DefaultAccountAndState(@DefaultAccountState int state, - @Nullable Account cloudAccount) { + @Nullable Account account) { if (!isValidDefaultAccountState(state)) { throw new IllegalArgumentException("Invalid default account state."); } - if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) { + if (isCloudOrSimAccount(state) != (account != null)) { throw new IllegalArgumentException( - "Default account can be set to cloud if and only if the cloud " + "Default account can be set to cloud or SIM if and only if the " + "account is provided."); } this.mState = state; - this.mCloudAccount = - (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null; + this.mAccount = isCloudOrSimAccount(state) ? account : null; } /** @@ -3118,6 +3130,21 @@ public final class ContactsContract { return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount); } + + /** + * Creates a `DefaultAccountAndState` instance representing a default account + * that is set to the sim and associated with the specified sim account. + * + * @param simAccount The non-null sim account associated with the default + * contacts account. + * @return A new `DefaultAccountAndState` instance with state + * {@link #DEFAULT_ACCOUNT_STATE_SIM}. + */ + public static @NonNull DefaultAccountAndState ofSim( + @NonNull Account simAccount) { + return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_SIM, simAccount); + } + /** * Creates a `DefaultAccountAndState` instance representing a default account * that is set to the local device storage. @@ -3140,6 +3167,18 @@ public final class ContactsContract { return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null); } + private static boolean isCloudOrSimAccount(@DefaultAccountState int state) { + return state == DEFAULT_ACCOUNT_STATE_CLOUD + || state == DEFAULT_ACCOUNT_STATE_SIM; + } + + private static boolean isValidDefaultAccountState(int state) { + return state == DEFAULT_ACCOUNT_STATE_NOT_SET + || state == DEFAULT_ACCOUNT_STATE_LOCAL + || state == DEFAULT_ACCOUNT_STATE_CLOUD + || state == DEFAULT_ACCOUNT_STATE_SIM; + } + /** * @return the state of the default account. */ @@ -3149,16 +3188,17 @@ public final class ContactsContract { } /** - * @return the cloud account associated with the default account, or null if the - * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}. + * @return the cloud account associated with the default account if the + * state is {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or + * {@link #DEFAULT_ACCOUNT_STATE_SIM}. */ - public @Nullable Account getCloudAccount() { - return mCloudAccount; + public @Nullable Account getAccount() { + return mAccount; } @Override public int hashCode() { - return Objects.hash(mState, mCloudAccount); + return Objects.hash(mState, mAccount); } @Override @@ -3170,14 +3210,8 @@ public final class ContactsContract { return false; } - return mState == that.mState && Objects.equals(mCloudAccount, - that.mCloudAccount); - } - - private static boolean isValidDefaultAccountState(int state) { - return state == DEFAULT_ACCOUNT_STATE_NOT_SET - || state == DEFAULT_ACCOUNT_STATE_LOCAL - || state == DEFAULT_ACCOUNT_STATE_CLOUD; + return mState == that.mState && Objects.equals(mAccount, + that.mAccount); } /** @@ -3189,7 +3223,8 @@ public final class ContactsContract { @IntDef( prefix = {"DEFAULT_ACCOUNT_STATE_"}, value = {DEFAULT_ACCOUNT_STATE_NOT_SET, - DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD}) + DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD, + DEFAULT_ACCOUNT_STATE_SIM}) public @interface DefaultAccountState { } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index b8a8be159d122f93356e3e485e63d2f09e3e2f6f..d82af55e2771c86e8c9e74401147d4c90f4075bc 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10749,6 +10749,16 @@ public final class Settings { public static final String LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS = "lock_screen_show_only_unseen_notifications"; + /** + * Indicates whether to minimalize the number of notifications to show on the lockscreen. + *

              + * Type: int (0 for false, 1 for true) + * + * @hide + */ + public static final String LOCK_SCREEN_NOTIFICATION_MINIMALISM = + "lock_screen_notification_minimalism"; + /** * Indicates whether snooze options should be shown on notifications *

              diff --git a/core/java/android/ranging/mock/RangingFrameworkInitializer.java b/core/java/android/ranging/mock/RangingFrameworkInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..540f51954a9c68ee24e16f4c2e9137f9f5cd2c70 --- /dev/null +++ b/core/java/android/ranging/mock/RangingFrameworkInitializer.java @@ -0,0 +1,34 @@ +/* + * 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.ranging; + +/** +* Mock RangingFrameworkInitializer. +* +* @hide +*/ + +// TODO(b/331206299): Remove this after RANGING_STACK_ENABLED is ramped up to next. +public final class RangingFrameworkInitializer { + private RangingFrameworkInitializer() {} + /** + * @hide + */ + public static void registerServiceWrappers() { + // No-op. + } +} diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS index c38ee089a5c24b6348b9db90c38c43a763a5ce8f..325d2742c2da470e7c5992eb79d76a16a0d96da5 100644 --- a/core/java/android/security/OWNERS +++ b/core/java/android/security/OWNERS @@ -10,3 +10,4 @@ per-file Confirmation*.java = file:/keystore/OWNERS per-file FileIntegrityManager.java = file:platform/system/security:/fsverity/OWNERS per-file IFileIntegrityService.aidl = file:platform/system/security:/fsverity/OWNERS per-file *.aconfig = victorhsieh@google.com,eranm@google.com +per-file *responsible_apis_flags.aconfig = haok@google.com \ No newline at end of file diff --git a/core/java/android/security/forensic/OWNERS b/core/java/android/security/forensic/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..d9e82a6e42f229a51886982407339880c1be5f18 --- /dev/null +++ b/core/java/android/security/forensic/OWNERS @@ -0,0 +1,8 @@ +# Bug component: 1630302 + +lizprucka@google.com +mteffeteller@google.com +myriamleggieri@google.com +rmneal@google.com +wenhaowang@google.com +willcoster@google.com diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig index 56d3669ac50c300640dbcb3392694c9b31a9181a..5457bbee8ad337888ab7bf51d77c327f5cdc6587 100644 --- a/core/java/android/security/responsible_apis_flags.aconfig +++ b/core/java/android/security/responsible_apis_flags.aconfig @@ -23,6 +23,17 @@ flag { bug: "230590090" } +flag { + name: "asm_reintroduce_grace_period" + namespace: "responsible_apis" + description: "Allow launches within the grace period for ASM apps" + bug: "367702727" + metadata { + purpose: PURPOSE_BUGFIX + } +} + + flag { name: "content_uri_permission_apis" is_exported: true @@ -52,3 +63,11 @@ flag { description: "Opt the system into enforcement of BAL" bug: "339403750" } + +flag { + name: "prevent_intent_redirect" + namespace: "responsible_apis" + description: "Prevent intent redirect attacks" + bug: "361143368" + is_fixed_read_only: true +} \ No newline at end of file diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 88da8ebc3f95006eb8ea507181cfc1cfce501f1c..48d7cf768e772cc74343ad8f3e73ecf05651312b 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -18,6 +18,7 @@ package android.service.notification; import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -90,10 +91,11 @@ public abstract class NotificationAssistantService extends NotificationListenerS = "android.service.notification.NotificationAssistantService"; /** - * Activity Action: Show notification assistant detail setting page in NAS app. + * Activity Action: Show notification assistant detail setting page in the NAS app. *

              - * In some cases, a matching Activity may not exist, so ensure you - * safeguard against this. + * To be implemented by the NAS to offer users additional customization of intelligence + * features. If the action is not implemented, the OS will not provide a link to it in the + * Settings UI. *

              * Input: Nothing. *

              @@ -103,6 +105,30 @@ public abstract class NotificationAssistantService extends NotificationListenerS public static final String ACTION_NOTIFICATION_ASSISTANT_DETAIL_SETTINGS = "android.service.notification.action.NOTIFICATION_ASSISTANT_DETAIL_SETTINGS"; + /** + * Activity Action: Open notification assistant feedback page in the NAS app. + *

              + * If the NAS does not implement this page, the OS will not show any feedback calls to action in + * the UI. + *

              + * Input: {@link #EXTRA_NOTIFICATION_KEY}, the {@link StatusBarNotification#getKey()} of the + * notification the user wants to file feedback for. + *

              + * Output: Nothing. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS = + "android.service.notification.action.NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS"; + + /** + * A string extra containing the key of the notification that the user triggered feedback for. + * + * Extra for {@link #ACTION_NOTIFICATION_ASSISTANT_FEEDBACK_SETTINGS}. + */ + @FlaggedApi(Flags.FLAG_NOTIFICATION_CLASSIFICATION) + public static final String EXTRA_NOTIFICATION_KEY + = "android.service.notification.extra.NOTIFICATION_KEY"; /** * Data type: int, the feedback rating score provided by user. The score can be any integer diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index d45b24ed69beaa0995d5083b04f5efc19c1ec1d5..303197dfd82d2f206e4d8f17690242d4389870ac 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -202,10 +202,8 @@ public class ZenModeConfig implements Parcelable { private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR; public static final String MANUAL_RULE_ID = "MANUAL_RULE"; - public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE"; + public static final String EVENTS_OBSOLETE_RULE_ID = "EVENTS_DEFAULT_RULE"; public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE"; - public static final List DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID, - EVENTS_DEFAULT_RULE_ID); public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; @@ -424,21 +422,10 @@ public class ZenModeConfig implements Parcelable { return policy; } + @FlaggedApi(Flags.FLAG_MODES_UI) public static ZenModeConfig getDefaultConfig() { ZenModeConfig config = new ZenModeConfig(); - EventInfo eventInfo = new EventInfo(); - eventInfo.reply = REPLY_YES_OR_MAYBE; - ZenRule events = new ZenRule(); - events.id = EVENTS_DEFAULT_RULE_ID; - events.conditionId = toEventConditionId(eventInfo); - events.component = ComponentName.unflattenFromString( - "android/com.android.server.notification.EventConditionProvider"); - events.enabled = false; - events.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - events.pkg = "android"; - config.automaticRules.put(EVENTS_DEFAULT_RULE_ID, events); - ScheduleInfo scheduleInfo = new ScheduleInfo(); scheduleInfo.days = new int[] {1, 2, 3, 4, 5, 6, 7}; scheduleInfo.startHour = 22; @@ -457,6 +444,13 @@ public class ZenModeConfig implements Parcelable { return config; } + // TODO: b/368247671 - Can be made a constant again when modes_ui is inlined + public static List getDefaultRuleIds() { + return Flags.modesUi() + ? List.of(EVERY_NIGHT_DEFAULT_RULE_ID) + : List.of(EVERY_NIGHT_DEFAULT_RULE_ID, EVENTS_OBSOLETE_RULE_ID); + } + void ensureManualZenRule() { if (manualRule == null) { final ZenRule newRule = new ZenRule(); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 384add5cf92958fd8d1aa3617d1c576fbbebd846..2ab16e91d987d608abcac407a0dad7cdc4352a99 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -2397,7 +2397,11 @@ public abstract class WallpaperService extends Service { // it hasn't changed and there is no need to update. ret = mBlastBufferQueue.createSurface(); } else { - mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format); + if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) { + mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format); + } else { + Log.w(TAG, "Skipping BlastBufferQueue update - invalid surface control"); + } } return ret; diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index c83285a5c889b1cafdcdfaa0bce02e54895d87f1..3599332af955ff84cdafd782aae88dd58b3bcf3d 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -168,3 +168,13 @@ flag { description: "Decouple variation settings, weight and style information from Typeface class" bug: "361260253" } + +flag { + name: "handwriting_track_disabled" + namespace: "text" + description: "Handwriting initiator tracks focused view even if handwriting is disabled to fix initiation bug." + bug: "361256391" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index ab9bd1fdfd726c5a910eeedfd3fb01b05e3023c4..f1329635f16ce3bb665f4d4e11b5649c98972bad 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -17,6 +17,7 @@ package android.view; import static com.android.text.flags.Flags.handwritingCursorPosition; +import static com.android.text.flags.Flags.handwritingTrackDisabled; import static com.android.text.flags.Flags.handwritingUnsupportedMessage; import android.annotation.FlaggedApi; @@ -352,7 +353,7 @@ public class HandwritingInitiator { final View focusedView = getFocusedView(); - if (!view.isAutoHandwritingEnabled()) { + if (!handwritingTrackDisabled() && !view.isAutoHandwritingEnabled()) { clearFocusedView(focusedView); return; } @@ -363,7 +364,8 @@ public class HandwritingInitiator { updateFocusedView(view); if (mState != null && mState.mPendingFocusedView != null - && mState.mPendingFocusedView.get() == view) { + && mState.mPendingFocusedView.get() == view + && (!handwritingTrackDisabled() || view.isAutoHandwritingEnabled())) { startHandwriting(view); } } @@ -416,7 +418,7 @@ public class HandwritingInitiator { */ @VisibleForTesting public boolean updateFocusedView(@NonNull View view) { - if (!view.shouldInitiateHandwriting()) { + if (!handwritingTrackDisabled() && !view.shouldInitiateHandwriting()) { mFocusedView = null; return false; } @@ -424,8 +426,10 @@ public class HandwritingInitiator { final View focusedView = getFocusedView(); if (focusedView != view) { mFocusedView = new WeakReference<>(view); - // A new view just gain focus. By default, we should show hover icon for it. - mShowHoverIconForConnectedView = true; + if (!handwritingTrackDisabled() || view.shouldInitiateHandwriting()) { + // A new view just gain focus. By default, we should show hover icon for it. + mShowHoverIconForConnectedView = true; + } } return true; diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index e90b1c0fc16749cdfd883ba633c380b36b404f34..229e8ee75844a53d2c4fd627667b4e8a6423bbc7 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -26,7 +26,6 @@ import android.annotation.Nullable; import android.os.IBinder; import android.os.Trace; import android.util.proto.ProtoOutputStream; -import android.view.SurfaceControl.Transaction; import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; @@ -34,8 +33,6 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.SoftInputShowHideReason; -import java.util.function.Supplier; - /** * Controls the visibility and animations of IME window insets source. * @hide @@ -54,10 +51,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { */ private boolean mIsRequestedVisibleAwaitingLeash; - public ImeInsetsSourceConsumer( - int id, InsetsState state, Supplier transactionSupplier, - InsetsController controller) { - super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller); + public ImeInsetsSourceConsumer(int id, InsetsState state, InsetsController controller) { + super(id, WindowInsets.Type.ime(), state, controller); } @Override diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java index 04bb6091672b3936850098a27691c8abcb5f5b89..a0d8a173c3e5ec8fe0ae31fad4e5b89b53a6cfce 100644 --- a/core/java/android/view/InsetsAnimationControlCallbacks.java +++ b/core/java/android/view/InsetsAnimationControlCallbacks.java @@ -53,13 +53,6 @@ public interface InsetsAnimationControlCallbacks { */ void notifyFinished(InsetsAnimationControlRunner runner, boolean shown); - /** - * Apply the new params to the surface. - * @param params The {@link android.view.SyncRtSurfaceTransactionApplier.SurfaceParams} to - * apply. - */ - void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params); - /** * Post a message to release the Surface, guaranteed to happen after all * previous calls to applySurfaceParams. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 91e9230cdc6a305cf9d793d833719f64fc1f31f3..97facc1ba4727f2844325adfb499c4015bf4c4c8 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -99,6 +99,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro private final @InsetsType int mTypes; private @InsetsType int mControllingTypes; private final InsetsAnimationControlCallbacks mController; + private final SurfaceParamsApplier mSurfaceParamsApplier; private final WindowInsetsAnimation mAnimation; private final long mDurationMs; private final Interpolator mInterpolator; @@ -123,6 +124,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro public InsetsAnimationControlImpl(SparseArray controls, @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @InsetsType int types, InsetsAnimationControlCallbacks controller, + SurfaceParamsApplier surfaceParamsApplier, InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) { @@ -131,6 +133,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mTypes = types; mControllingTypes = types; mController = controller; + mSurfaceParamsApplier = surfaceParamsApplier; mInitialInsetsState = new InsetsState(state, true /* copySources */); if (frame != null) { final SparseIntArray idSideMap = new SparseIntArray(); @@ -257,6 +260,11 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro return mAnimationType; } + @Override + public SurfaceParamsApplier getSurfaceParamsApplier() { + return mSurfaceParamsApplier; + } + @Override @Nullable public ImeTracker.Token getStatsToken() { @@ -305,7 +313,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha); updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha); - mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); + mSurfaceParamsApplier.applySurfaceParams(params.toArray(new SurfaceParams[params.size()])); mCurrentInsets = mPendingInsets; mAnimation.setFraction(mPendingFraction); mCurrentAlpha = mPendingAlpha; diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java index 8cb8b47dd0ec56a0efdf52ea4fd5fe1d92d0c8a9..4f102da4692a69ca9440440905ae529bd20ecc1b 100644 --- a/core/java/android/view/InsetsAnimationControlRunner.java +++ b/core/java/android/view/InsetsAnimationControlRunner.java @@ -76,6 +76,11 @@ public interface InsetsAnimationControlRunner { */ @AnimationType int getAnimationType(); + /** + * @return The {@link SurfaceParamsApplier} this runner is using. + */ + SurfaceParamsApplier getSurfaceParamsApplier(); + /** * @return The token tracking the current IME request or {@code null} otherwise. */ @@ -99,4 +104,27 @@ public interface InsetsAnimationControlRunner { * @param fieldId FieldId of the implementation class */ void dumpDebug(ProtoOutputStream proto, long fieldId); + + /** + * Interface applying given surface operations. + */ + interface SurfaceParamsApplier { + + SurfaceParamsApplier DEFAULT = params -> { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.applyParams(t, params[i], new float[9]); + } + t.apply(); + t.close(); + }; + + /** + * Apply the new params to the surface. + * + * @param params The {@link SyncRtSurfaceTransactionApplier.SurfaceParams} to apply. + */ + void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params); + + } } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index fc185bc737351c22cfbc702e368ea75c65ffe477..8c2c4951a9f740eb09846ab5cc3b3968db18cc86 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -17,7 +17,6 @@ package android.view; import static android.view.InsetsController.DEBUG; -import static android.view.SyncRtSurfaceTransactionApplier.applyParams; import android.annotation.Nullable; import android.annotation.UiThread; @@ -30,7 +29,6 @@ import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; import android.view.InsetsController.LayoutInsetsDuringAnimation; -import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; import android.view.inputmethod.ImeTracker; @@ -50,8 +48,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro private final InsetsAnimationControlCallbacks mCallbacks = new InsetsAnimationControlCallbacks() { - private final float[] mTmpFloat9 = new float[9]; - @Override @UiThread public @@ -80,19 +76,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro mOuterCallbacks.notifyFinished(InsetsAnimationThreadControlRunner.this, shown)); } - @Override - public void applySurfaceParams(SurfaceParams... params) { - if (DEBUG) Log.d(TAG, "applySurfaceParams"); - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = params.length - 1; i >= 0; i--) { - SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i]; - applyParams(t, surfaceParams, mTmpFloat9); - } - t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); - t.apply(); - t.close(); - } - @Override public void releaseSurfaceControlFromRt(SurfaceControl sc) { if (DEBUG) Log.d(TAG, "releaseSurfaceControlFromRt"); @@ -106,6 +89,22 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } }; + private SurfaceParamsApplier mSurfaceParamsApplier = new SurfaceParamsApplier() { + + private final float[] mTmpFloat9 = new float[9]; + + @Override + public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = params.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.applyParams(t, params[i], mTmpFloat9); + } + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + t.apply(); + t.close(); + } + }; + @UiThread public InsetsAnimationThreadControlRunner(SparseArray controls, @Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener, @@ -117,8 +116,8 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro mMainThreadHandler = mainThreadHandler; mOuterCallbacks = controller; mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types, - mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation, - translator, statsToken); + mCallbacks, mSurfaceParamsApplier, insetsAnimationSpec, animationType, + layoutInsetsDuringAnimation, translator, statsToken); InsetsAnimationThread.getHandler().post(() -> { if (mControl.isCancelled()) { return; @@ -186,6 +185,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro return mControl.getAnimationType(); } + @Override + public SurfaceParamsApplier getSurfaceParamsApplier() { + return mSurfaceParamsApplier; + } + @Override public void updateLayoutInsetsDuringAnimation( @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 8fdf91a2d87cf3bd00aaa353bf6e5192e398f0b6..8ac5532493780c6ee46d36a544d652840b5481f9 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -29,7 +29,6 @@ import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.ime; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; -import static com.android.window.flags.Flags.insetsControlSeq; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -55,7 +54,6 @@ import android.util.Pair; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsSourceConsumer.ShowResult; -import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; @@ -85,7 +83,8 @@ import java.util.Objects; * Implements {@link WindowInsetsController} on the client. * @hide */ -public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks { +public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks, + InsetsAnimationControlRunner.SurfaceParamsApplier { private int mTypesBeingCancelled; @@ -307,7 +306,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** Not running an animation. */ - @VisibleForTesting public static final int ANIMATION_TYPE_NONE = -1; /** Running animation will show insets */ @@ -317,11 +315,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public static final int ANIMATION_TYPE_HIDE = 1; /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */ - @VisibleForTesting(visibility = PACKAGE) public static final int ANIMATION_TYPE_USER = 2; /** Running animation will resize insets */ - @VisibleForTesting public static final int ANIMATION_TYPE_RESIZE = 3; @Retention(RetentionPolicy.SOURCE) @@ -757,11 +753,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public InsetsController(Host host) { this(host, (controller, id, type) -> { if (!Flags.refactorInsetsController() && type == ime()) { - return new ImeInsetsSourceConsumer(id, controller.mState, - Transaction::new, controller); + return new ImeInsetsSourceConsumer(id, controller.mState, controller); } else { - return new InsetsSourceConsumer(id, type, controller.mState, - Transaction::new, controller); + return new InsetsSourceConsumer(id, type, controller.mState, controller); } }, host.getHandler()); } @@ -882,9 +876,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @InsetsType int visibleTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (int i = 0, size = newState.sourceSize(); i < size; i++) { - final InsetsSource source = insetsControlSeq() - ? new InsetsSource(newState.sourceAt(i)) - : newState.sourceAt(i); + final InsetsSource source = new InsetsSource(newState.sourceAt(i)); @InsetsType int type = source.getType(); @AnimationType int animationType = getAnimationType(type); final InsetsSourceConsumer consumer = mSourceConsumers.get(source.getId()); @@ -1525,9 +1517,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation insetsAnimationSpec, animationType, layoutInsetsDuringAnimation, mHost.getTranslator(), mHost.getHandler(), statsToken) : new InsetsAnimationControlImpl(controls, - frame, mState, listener, typesReady, this, insetsAnimationSpec, + frame, mState, listener, typesReady, this, this, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation, mHost.getTranslator(), statsToken); + for (int i = controls.size() - 1; i >= 0; i--) { + final InsetsSourceConsumer consumer = mSourceConsumers.get(controls.keyAt(i)); + if (consumer != null) { + consumer.setSurfaceParamsApplier(runner.getSurfaceParamsApplier()); + } + } if ((typesReady & WindowInsets.Type.ime()) != 0) { ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl", mHost.getInputMethodManager(), null /* icProto */); diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java index f90b8411e333957381dc399a0376c4c3a78ca48c..5262751cc6ed76e5bcefe7f4ac66f23816ff6eee 100644 --- a/core/java/android/view/InsetsResizeAnimationRunner.java +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -93,6 +93,11 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner return ANIMATION_TYPE_RESIZE; } + @Override + public SurfaceParamsApplier getSurfaceParamsApplier() { + return SurfaceParamsApplier.DEFAULT; + } + @Override @Nullable public ImeTracker.Token getStatsToken() { diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index 391d757365e6802dba8f7830884503a12d544a2a..da788a78a95fca707c34c6158bbc979c25f8e28c 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,6 +17,7 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; +import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE; @@ -28,16 +29,16 @@ import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL; import static android.view.InsetsSourceConsumerProto.TYPE_NUMBER; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; -import static com.android.window.flags.Flags.insetsControlSeq; import android.annotation.IntDef; import android.annotation.Nullable; +import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; import android.util.proto.ProtoOutputStream; -import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.Flags; import android.view.inputmethod.ImeTracker; @@ -48,7 +49,6 @@ import com.android.internal.inputmethod.ImeTracing; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; -import java.util.function.Supplier; /** * Controls the visibility and animations of a single window insets source. @@ -92,10 +92,12 @@ public class InsetsSourceConsumer { private final int mType; private static final String TAG = "InsetsSourceConsumer"; - private final Supplier mTransactionSupplier; @Nullable private InsetsSourceControl mSourceControl; private boolean mHasWindowFocus; + private InsetsAnimationControlRunner.SurfaceParamsApplier mSurfaceParamsApplier = + InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT; + private final Matrix mTmpMatrix = new Matrix(); /** * Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}. @@ -108,16 +110,13 @@ public class InsetsSourceConsumer { * @param id The ID of the consumed insets. * @param type The {@link InsetsType} of the consumed insets. * @param state The current {@link InsetsState} of the consumed insets. - * @param transactionSupplier The source of new {@link Transaction} instances. The supplier - * must provide *new* instances, which will be explicitly closed by this class. * @param controller The {@link InsetsController} to use for insets interaction. */ public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state, - Supplier transactionSupplier, InsetsController controller) { + InsetsController controller) { mId = id; mType = type; mState = state; - mTransactionSupplier = transactionSupplier; mController = controller; } @@ -162,6 +161,9 @@ public class InsetsSourceConsumer { if (localVisible != serverVisible) { mController.notifyVisibilityChanged(); } + + // Reset the applier to the default one which has the most lightweight implementation. + setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT); } else { final boolean requestedVisible = isRequestedVisibleAwaitingControl(); final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null; @@ -184,10 +186,11 @@ public class InsetsSourceConsumer { mController.notifyVisibilityChanged(); } - // If we have a new leash, make sure visibility is up-to-date, even though we - // didn't want to run an animation above. - if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) { - applyRequestedVisibilityToControl(); + // If there is no animation controlling the leash, make sure the visibility and the + // position is up-to-date. Note: ANIMATION_TYPE_RESIZE doesn't control the leash. + final int animType = mController.getAnimationType(mType); + if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) { + applyRequestedVisibilityAndPositionToControl(); } // Remove the surface that owned by last control when it lost. @@ -228,6 +231,15 @@ public class InsetsSourceConsumer { return mType; } + /** + * Sets the SurfaceParamsApplier that the latest animation runner is using. The leash owned by + * this class is always applied by the applier, so that the transaction order can always be + * aligned with the calling sequence. + */ + void setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier) { + mSurfaceParamsApplier = applier; + } + /** * Called right after the animation is started or finished. */ @@ -418,9 +430,6 @@ public class InsetsSourceConsumer { // Frame is changing while animating. Keep note of the new frame but keep existing frame // until animation is finished. - if (!insetsControlSeq()) { - newSource = new InsetsSource(newSource); - } mPendingFrame = new Rect(newSource.getFrame()); mPendingVisibleFrame = newSource.getVisibleFrame() != null ? new Rect(newSource.getVisibleFrame()) @@ -431,24 +440,30 @@ public class InsetsSourceConsumer { if (DEBUG) Log.d(TAG, "updateSource: " + newSource); } - private void applyRequestedVisibilityToControl() { - if (mSourceControl == null || mSourceControl.getLeash() == null) { + private void applyRequestedVisibilityAndPositionToControl() { + if (mSourceControl == null) { return; } - - final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0; - try (Transaction t = mTransactionSupplier.get()) { - if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible); - if (requestedVisible) { - t.show(mSourceControl.getLeash()); - } else { - t.hide(mSourceControl.getLeash()); - } - // Ensure the alpha value is aligned with the actual requested visibility. - t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0); - t.apply(); + final SurfaceControl leash = mSourceControl.getLeash(); + if (leash == null) { + return; } - onPerceptible(requestedVisible); + + final boolean visible = (mController.getRequestedVisibleTypes() & mType) != 0; + final Point surfacePosition = mSourceControl.getSurfacePosition(); + + if (DEBUG) Log.d(TAG, "applyRequestedVisibilityAndPositionToControl: visible=" + visible + + " position=" + surfacePosition); + + mTmpMatrix.setTranslate(surfacePosition.x, surfacePosition.y); + mSurfaceParamsApplier.applySurfaceParams( + new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(leash) + .withVisibility(visible) + .withAlpha(visible ? 1 : 0) + .withMatrix(mTmpMatrix) + .build()); + + onPerceptible(visible); } void dumpDebug(ProtoOutputStream proto, long fieldId) { diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 2dda835436bc6ff7a6a95061133fbce4e08417fe..90ceb440ea5ef05523249d42c829b1217e89a671 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -4524,6 +4524,21 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * TODO(b/366484871): To be removed once we have some logging in native + * This is called when BlastBufferQueue.mergeWithNextTransaction() is called from java, and + * for the purposes of logging that path. + */ + void onMergeWithNextTransaction(CharSequence windowName) { + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "merge", this, null, "window=" + windowName); + if (mCalls != null) { + mCalls.clear(); + } + } + } + /** * Equivalent to reparent with a null parent, in that it removes * the SurfaceControl from the scene, but it also releases diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d46e1f29597e5b96f32975196dc135aef499ce42..ac208b57788d240968d1cf1dd525476aac3eb2e9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -130,8 +130,6 @@ import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; -import static com.android.window.flags.Flags.insetsControlChangedItem; -import static com.android.window.flags.Flags.insetsControlSeq; import static com.android.window.flags.Flags.setScPropertiesInClient; import static com.android.window.flags.Flags.systemUiImmersiveConfirmationDialog; @@ -889,10 +887,7 @@ public final class ViewRootImpl implements ViewParent, /** Non-{@code null} if {@link #mActivityConfigCallback} is not {@code null}. */ @Nullable private ActivityWindowInfo mLastReportedActivityWindowInfo; - @Nullable - private final ClientWindowFrames mLastReportedFrames = insetsControlSeq() - ? new ClientWindowFrames() - : null; + private final ClientWindowFrames mLastReportedFrames = new ClientWindowFrames(); private int mLastReportedInsetsStateSeq = getInitSeq(); private int mLastReportedActiveControlsSeq = getInitSeq(); @@ -2069,7 +2064,11 @@ public final class ViewRootImpl implements ViewParent, if (mAttachInfo.mThreadedRenderer == null) return; if (mAttachInfo.mThreadedRenderer.setForceDark(determineForceDarkType())) { // TODO: Don't require regenerating all display lists to apply this setting - invalidateWorld(mView); + if (forceInvertColor()) { + destroyAndInvalidate(); + } else { + invalidateWorld(mView); + } } } @@ -2317,9 +2316,6 @@ public final class ViewRootImpl implements ViewParent, } private void onClientWindowFramesChanged(@NonNull ClientWindowFrames inOutFrames) { - if (mLastReportedFrames == null) { - return; - } if (isIncomingSeqStale(mLastReportedFrames.seq, inOutFrames.seq)) { // If the incoming is stale, use the last reported instead. inOutFrames.setTo(mLastReportedFrames); @@ -2330,14 +2326,12 @@ public final class ViewRootImpl implements ViewParent, } private void onInsetsStateChanged(@NonNull InsetsState insetsState) { - if (insetsControlSeq()) { - if (isIncomingSeqStale(mLastReportedInsetsStateSeq, insetsState.getSeq())) { - // The incoming is stale. Skip. - return; - } - // Keep track of the latest. - mLastReportedInsetsStateSeq = insetsState.getSeq(); + if (isIncomingSeqStale(mLastReportedInsetsStateSeq, insetsState.getSeq())) { + // The incoming is stale. Skip. + return; } + // Keep track of the latest. + mLastReportedInsetsStateSeq = insetsState.getSeq(); if (mTranslator != null) { mTranslator.translateInsetsStateInScreenToAppWindow(insetsState); @@ -2352,15 +2346,13 @@ public final class ViewRootImpl implements ViewParent, return; } - if (insetsControlSeq()) { - if (isIncomingSeqStale(mLastReportedActiveControlsSeq, activeControls.getSeq())) { - // The incoming is stale. Skip. - activeControls.release(); - return; - } - // Keep track of the latest. - mLastReportedActiveControlsSeq = activeControls.getSeq(); + if (isIncomingSeqStale(mLastReportedActiveControlsSeq, activeControls.getSeq())) { + // The incoming is stale. Skip. + activeControls.release(); + return; } + // Keep track of the latest. + mLastReportedActiveControlsSeq = activeControls.getSeq(); final InsetsSourceControl[] controls = activeControls.get(); if (mTranslator != null) { @@ -11519,12 +11511,8 @@ public final class ViewRootImpl implements ViewParent, public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl.Array activeControls) { final boolean isFromInsetsControlChangeItem; - if (insetsControlChangedItem()) { - isFromInsetsControlChangeItem = mIsFromTransactionItem; - mIsFromTransactionItem = false; - } else { - isFromInsetsControlChangeItem = false; - } + isFromInsetsControlChangeItem = mIsFromTransactionItem; + mIsFromTransactionItem = false; final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor == null) { if (isFromInsetsControlChangeItem) { @@ -11927,14 +11915,22 @@ public final class ViewRootImpl implements ViewParent, public void onHighTextContrastStateChanged(boolean enabled) { ThreadedRenderer.setHighContrastText(enabled); - // Destroy Displaylists so they can be recreated with high contrast recordings - destroyHardwareResources(); - - // Schedule redraw, which will rerecord + redraw all text - invalidate(); + destroyAndInvalidate(); } } + /** + * Destroy Displaylists so they can be recreated with new recordings, in case you are changing + * the way things are rendered (e.g. high contrast, force dark), then invalidate to trigger a + * redraw. + */ + private void destroyAndInvalidate() { + destroyHardwareResources(); + + // Schedule redraw, which will rerecord + redraw all text + invalidate(); + } + /** * This class is an interface this ViewAncestor provides to the * AccessibilityManagerService to the latter can interact with @@ -12407,6 +12403,7 @@ public final class ViewRootImpl implements ViewParent, transaction.setBlurRegions(surfaceControl, regionCopy); if (mBlastBufferQueue != null) { + transaction.onMergeWithNextTransaction(getTitle()); mBlastBufferQueue.mergeWithNextTransaction(transaction, frameNumber); } } @@ -12433,6 +12430,9 @@ public final class ViewRootImpl implements ViewParent, */ public void mergeWithNextTransaction(Transaction t, long frameNumber) { if (mBlastBufferQueue != null) { + if (t != null) { + t.onMergeWithNextTransaction(getTitle()); + } mBlastBufferQueue.mergeWithNextTransaction(t, frameNumber); } else { t.apply(); diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index dda399357d8c8840e9dcfb1d57a2f690b6e6663b..d5ccca992b4fd80b0b4bee59cdbe0b60b91bc8b5 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -157,10 +157,10 @@ public class WindowLayout { // which prevents overlap with the DisplayCutout. if (!attachedInParent && !floatingInScreenWindow) { mTempRect.set(outParentFrame); - outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars); + intersectOrClamp(outParentFrame, displayCutoutSafeExceptMaybeBars); frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame); } - outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars); + intersectOrClamp(outDisplayFrame, displayCutoutSafeExceptMaybeBars); } final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0; @@ -283,6 +283,19 @@ public class WindowLayout { + " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes)); } + /** + * If both rectangles intersect, set inOutRect to that intersection. Otherwise, clamp inOutRect + * to the side (or the corner) that the other rectangle is away from. + * Unlike {@link Rect#intersectUnchecked(Rect)}, this method guarantees that the new rectangle + * is valid and contained in inOutRect if rectangles involved are valid. + */ + private static void intersectOrClamp(Rect inOutRect, Rect other) { + inOutRect.left = Math.min(Math.max(inOutRect.left, other.left), inOutRect.right); + inOutRect.top = Math.min(Math.max(inOutRect.top, other.top), inOutRect.bottom); + inOutRect.right = Math.max(Math.min(inOutRect.right, other.right), inOutRect.left); + inOutRect.bottom = Math.max(Math.min(inOutRect.bottom, other.bottom), inOutRect.top); + } + public static void extendFrameByCutout(Rect displayCutoutSafe, Rect displayFrame, Rect inOutFrame, Rect tempRect) { if (displayCutoutSafe.contains(inOutFrame)) { diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index c88048c17160c6ae6a077afa7b7d40899ffc7f71..1f341caa8ed3f4c537573e3e1423273f8ab9ec04 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -17,6 +17,7 @@ package android.view; import static android.os.IInputConstants.POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY; +import static android.os.IInputConstants.POLICY_FLAG_KEY_GESTURE_TRIGGERED; import android.annotation.IntDef; import android.os.PowerManager; @@ -35,6 +36,7 @@ public interface WindowManagerPolicyConstants { int FLAG_VIRTUAL = 0x00000002; int FLAG_INJECTED_FROM_ACCESSIBILITY = POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY; + int FLAG_KEY_GESTURE_TRIGGERED = POLICY_FLAG_KEY_GESTURE_TRIGGERED; int FLAG_INJECTED = 0x01000000; int FLAG_TRUSTED = 0x02000000; int FLAG_FILTERED = 0x04000000; diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index c92593f8155832a934056fdfdde1a8804229bf30..7b6e070f0008a77bbb2d496745cf5ea9569171d4 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -880,10 +880,6 @@ public final class AccessibilityWindowInfo implements Parcelable { * @hide */ public static String typeToString(int type) { - if (Flags.enableTypeWindowControl() && type == TYPE_WINDOW_CONTROL) { - return "TYPE_WINDOW_CONTROL"; - } - switch (type) { case TYPE_APPLICATION: { return "TYPE_APPLICATION"; @@ -903,8 +899,12 @@ public final class AccessibilityWindowInfo implements Parcelable { case TYPE_MAGNIFICATION_OVERLAY: { return "TYPE_MAGNIFICATION_OVERLAY"; } - default: + default: { + if (Flags.enableTypeWindowControl() && type == TYPE_WINDOW_CONTROL) { + return "TYPE_WINDOW_CONTROL"; + } return ""; + } } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 2f649c21fe08041f14030e8a808f5d476e32be62..1e5c6d8177e144edeb6923fb3fe9d656222f8358 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -464,13 +464,6 @@ public final class InputMethodManager { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) private static final long USE_ASYNC_SHOW_HIDE_METHOD = 352594277L; // This is a bug id. - /** - * Version-gating is guarded by bug-fix flag. - */ - private static final boolean ASYNC_SHOW_HIDE_METHOD_ENABLED = - !Flags.compatchangeForZerojankproxy() - || CompatChanges.isChangeEnabled(USE_ASYNC_SHOW_HIDE_METHOD); - /** * If {@code true}, avoid calling the * {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService} @@ -614,6 +607,15 @@ public final class InputMethodManager { @UnsupportedAppUsage Rect mCursorRect = new Rect(); + /** + * Version-gating is guarded by bug-fix flag. + */ + // Note: this is non-static so that it only gets initialized once CompatChanges has + // access to the correct application context. + private final boolean mAsyncShowHideMethodEnabled = + !Flags.compatchangeForZerojankproxy() + || CompatChanges.isChangeEnabled(USE_ASYNC_SHOW_HIDE_METHOD); + /** Cached value for {@link #isStylusHandwritingAvailable} for userId. */ @GuardedBy("mH") private PropertyInvalidatedCache mStylusHandwritingAvailableCache; @@ -2419,7 +2421,7 @@ public final class InputMethodManager { mCurRootView.getLastClickToolType(), resultReceiver, reason, - ASYNC_SHOW_HIDE_METHOD_ENABLED); + mAsyncShowHideMethodEnabled); } } } @@ -2463,7 +2465,7 @@ public final class InputMethodManager { mCurRootView.getLastClickToolType(), resultReceiver, reason, - ASYNC_SHOW_HIDE_METHOD_ENABLED); + mAsyncShowHideMethodEnabled); } } @@ -2572,7 +2574,7 @@ public final class InputMethodManager { return true; } else { return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, - statsToken, flags, resultReceiver, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED); + statsToken, flags, resultReceiver, reason, mAsyncShowHideMethodEnabled); } } } @@ -2615,7 +2617,7 @@ public final class InputMethodManager { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, view.getWindowToken(), - statsToken, flags, null, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED); + statsToken, flags, null, reason, mAsyncShowHideMethodEnabled); } } @@ -3392,7 +3394,7 @@ public final class InputMethodManager { servedInputConnection == null ? null : servedInputConnection.asIRemoteAccessibilityInputConnection(), view.getContext().getApplicationInfo().targetSdkVersion, targetUserId, - mImeDispatcher, ASYNC_SHOW_HIDE_METHOD_ENABLED); + mImeDispatcher, mAsyncShowHideMethodEnabled); } else { res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus( startInputReason, mClient, windowGainingFocus, startInputFlags, diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index cc5e583034a585f46375d147b6a06bbdedaf4749..fbc30ed3d8f50093ae8fb3723b2918d2a0027ef3 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -286,6 +286,13 @@ flag { bug: "350456942" } +flag { + name: "enable_fully_immersive_in_desktop" + namespace: "lse_desktop_experience" + description: "Enabled the fully immersive experience from desktop" + bug: "359523924" +} + flag { name: "enable_display_focus_in_shell_transitions" namespace: "lse_desktop_experience" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index e1402f8224ebb5d82ac96ad9708faa1caae4c99a..ccaaf6322f11378c3b4aab3eb353a922af933dc2 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -69,6 +69,17 @@ flag { bug: "291870756" } +flag { + name: "common_surface_animator" + namespace: "windowing_frontend" + description: "A reusable surface animator for default transition" + bug: "326331384" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "reduce_keyguard_transitions" namespace: "windowing_frontend" @@ -110,6 +121,17 @@ flag { bug: "320510464" } +flag { + name: "remove_activity_starter_dream_callback" + namespace: "windowing_frontend" + description: "Avoid a race with DreamManagerService callbacks for isDreaming by checking Activity state directly" + bug: "366452352" + metadata { + purpose: PURPOSE_BUGFIX + } + is_fixed_read_only: true +} + flag { name: "supports_multi_instance_system_ui" is_exported: true @@ -266,3 +288,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "remove_starting_window_wait_for_multi_transitions" + namespace: "windowing_frontend" + description: "Avoid remove starting window too early when playing multiple transitions" + bug: "362347290" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 9ae3fc1fa3f08d846c872964385e7fd542d28ccf..f0ea7a82d76331dbf1d681b6c6d54464cb07c8d7 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -66,27 +66,6 @@ flag { bug: "293658614" } -flag { - namespace: "windowing_sdk" - name: "insets_control_changed_item" - description: "Pass insetsControlChanged through ClientTransaction to fix the racing" - bug: "339380439" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - namespace: "windowing_sdk" - name: "insets_control_seq" - description: "Add seqId to InsetsControls to ensure the stale update is ignored" - bug: "339380439" - is_fixed_read_only: true - metadata { - purpose: PURPOSE_BUGFIX - } -} - flag { namespace: "windowing_sdk" name: "move_animation_options_to_change" diff --git a/core/java/com/android/internal/os/ApplicationSharedMemory.java b/core/java/com/android/internal/os/ApplicationSharedMemory.java new file mode 100644 index 0000000000000000000000000000000000000000..84f713edcc1ab675bc27a70306d48a8f91d09dca --- /dev/null +++ b/core/java/com/android/internal/os/ApplicationSharedMemory.java @@ -0,0 +1,296 @@ +/* + * 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.os; + +import android.annotation.NonNull; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.io.IoUtils; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.time.DateTimeException; + +/** + * This class is used to create and access a shared memory region. + * + *

              The intended use case is that memory is shared between system processes and application + * processes such that it's readable to all apps and writable only to system processes. + * + *

              This shared memory region can be used as an alternative to Binder IPC for driving + * communication between system processes and application processes at a lower latency and higher + * throughput than Binder IPC can provide, under circumstances where the additional features of + * Binder IPC are not required. + * + *

              Unlike Binder IPC, shared memory doesn't support synchronous transactions and associated + * ordering guarantees, client identity (and therefore caller permission checking), and access + * auditing. Therefore it's not a suitable alternative to Binder IPC for most use cases. + * + *

              Additionally, because the intended use case is to make this shared memory region readable to + * all apps, it's not suitable for sharing sensitive data. + * + * @see {@link ApplicationSharedMemoryTestRule} for unit testing support. + * @hide + */ +public class ApplicationSharedMemory implements AutoCloseable { + + // LINT.IfChange(invalid_network_time) + public static final long INVALID_NETWORK_TIME = -1; + // LINT.ThenChange(frameworks/base/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp:invalid_network_time) + + private static final boolean DEBUG = false; + private static final String LOG_TAG = "ApplicationSharedMemory"; + + @VisibleForTesting public static ApplicationSharedMemory sInstance; + + /** Get the process-global instance. */ + public static ApplicationSharedMemory getInstance() { + ApplicationSharedMemory instance = sInstance; + if (instance == null) { + throw new IllegalStateException("ApplicationSharedMemory not initialized"); + } + return instance; + } + + /** Set the process-global instance. */ + public static void setInstance(ApplicationSharedMemory instance) { + if (DEBUG) { + Log.d(LOG_TAG, "setInstance: " + instance); + } + + if (sInstance != null) { + throw new IllegalStateException("ApplicationSharedMemory already initialized"); + } + sInstance = instance; + } + + /** Allocate mutable shared memory region. */ + public static ApplicationSharedMemory create() { + if (DEBUG) { + Log.d(LOG_TAG, "create"); + } + + int fd = nativeCreate(); + FileDescriptor fileDescriptor = new FileDescriptor(); + fileDescriptor.setInt$(fd); + + final boolean mutable = true; + long ptr = nativeMap(fd, mutable); + nativeInit(ptr); + + return new ApplicationSharedMemory(fileDescriptor, mutable, ptr); + } + + /** + * Open shared memory region from a given {@link FileDescriptor}. + * + * @param fileDescriptor Handle to shared memory region. + * @param mutable Whether the shared memory region is mutable. If true, will be mapped as + * read-write memory. If false, will be mapped as read-only memory. Passing true (mutable) + * if |pfd| is a handle to read-only memory will result in undefined behavior. + */ + public static ApplicationSharedMemory fromFileDescriptor( + @NonNull FileDescriptor fileDescriptor, boolean mutable) { + if (DEBUG) { + Log.d(LOG_TAG, "fromFileDescriptor: " + fileDescriptor + " mutable: " + mutable); + } + + long ptr = nativeMap(fileDescriptor.getInt$(), mutable); + return new ApplicationSharedMemory(fileDescriptor, mutable, ptr); + } + + /** + * Allocate read-write shared memory region. + * + * @return File descriptor of the shared memory region. + */ + private static native int nativeCreate(); + + /** + * Map the shared memory region. + * + * @param fd File descriptor of the shared memory region. + * @param isMutable Whether the shared memory region is mutable. If true, will be mapped as + * read-write memory. If false, will be mapped as read-only memory. + * @return Pointer to the mapped shared memory region. + */ + private static native long nativeMap(int fd, boolean isMutable); + + /** + * Initialize read-write shared memory region. + * + * @param Pointer to the mapped shared memory region. + */ + private static native void nativeInit(long ptr); + + /** + * Unmap the shared memory region. + * + * @param ptr Pointer to the mapped shared memory region. + */ + private static native void nativeUnmap(long ptr); + + /** + * If true, this object owns the read-write instance of the shared memory region. If false, this + * object can only read. + */ + private final boolean mMutable; + + /** + * Handle to the shared memory region. This can be send to other processes over Binder calls or + * Intent extras. Recipients can use this handle to obtain read-only access to the shared memory + * region. + */ + private FileDescriptor mFileDescriptor; + + /** Native pointer to the mapped shared memory region. */ + private volatile long mPtr; + + ApplicationSharedMemory(@NonNull FileDescriptor fileDescriptor, boolean mutable, long ptr) { + mFileDescriptor = fileDescriptor; + mMutable = mutable; + mPtr = ptr; + } + + /** + * Returns the file descriptor of the shared memory region. + * + *

              This file descriptor retains the mutability properties of this object instance, and can be + * sent over Binder IPC or Intent extras to another process to allow the remote process to map + * the same shared memory region with the same access rights. + * + * @throws IllegalStateException if the file descriptor is closed. + */ + public FileDescriptor getFileDescriptor() { + checkFileOpen(); + return mFileDescriptor; + } + + /** + * Returns a read-only file descriptor of the shared memory region. This object can be sent over + * Binder IPC or Intent extras to another process to allow the remote process to map the same + * shared memory region with read-only access. + * + * @return a read-only handle to the shared memory region. + * @throws IllegalStateException if the file descriptor is closed. + */ + public FileDescriptor getReadOnlyFileDescriptor() throws IOException { + checkFileOpen(); + FileDescriptor readOnlyFileDescriptor = new FileDescriptor(); + int readOnlyFd = nativeDupAsReadOnly(mFileDescriptor.getInt$()); + readOnlyFileDescriptor.setInt$(readOnlyFd); + return readOnlyFileDescriptor; + } + + /** Return a read-only duplicate of the file descriptor. */ + private static native int nativeDupAsReadOnly(int fd); + + /** Set the latest network Unix Epoch minus realtime millis. */ + public void setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(long offset) { + checkMutable(); + nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(mPtr, offset); + } + + /** Clear the latest network Unix Epoch minus realtime millis. */ + public void clearLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis() { + checkMutable(); + nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( + mPtr, INVALID_NETWORK_TIME); + } + + @CriticalNative + private static native void nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( + long ptr, long offset); + + /** + * Get the latest network Unix Epoch minus realtime millis. + * + * @throws DateTimeException when no network time can be provided. + */ + public long getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis() + throws DateTimeException { + checkMapped(); + long offset = nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(mPtr); + if (offset == INVALID_NETWORK_TIME) { + throw new DateTimeException("No network time available"); + } + return offset; + } + + @CriticalNative + public static native long nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( + long ptr); + + /** + * Close the associated file descriptor. + * + *

              This method is safe to call if you never intend to pass the file descriptor to another + * process, whether via {@link #getFileDescriptor()} or {@link #getReadOnlyFileDescriptor()}. + * After calling this method, subsequent calls to {@link #getFileDescriptor()} or {@link + * #getReadOnlyFileDescriptor()} will throw an {@link IllegalStateException}. + */ + public void closeFileDescriptor() { + if (mFileDescriptor != null) { + IoUtils.closeQuietly(mFileDescriptor); + mFileDescriptor = null; + } + } + + public void close() { + if (mPtr != 0) { + nativeUnmap(mPtr); + mPtr = 0; + } + + if (mFileDescriptor != null) { + IoUtils.closeQuietly(mFileDescriptor); + mFileDescriptor = null; + } + } + + private void checkFileOpen() { + if (mFileDescriptor == null) { + throw new IllegalStateException("File descriptor is closed"); + } + } + + /** + * Check that the shared memory region is mapped. + * + * @throws IllegalStateException if the shared memory region is not mapped. + */ + private void checkMapped() { + if (mPtr == 0) { + throw new IllegalStateException("Instance is closed"); + } + } + + /** + * Check that the shared memory region is mapped and mutable. + * + * @throws IllegalStateException if the shared memory region is not mapped or not mutable. + */ + private void checkMutable() { + checkMapped(); + if (!mMutable) { + throw new IllegalStateException("Not mutable"); + } + } +} diff --git a/core/java/com/android/internal/os/OWNERS b/core/java/com/android/internal/os/OWNERS index 391d25757af40adb0647489c2af8a22ac3f5c9d6..ffd4499f929aa28e2225a6e75630563aa3dcbdd4 100644 --- a/core/java/com/android/internal/os/OWNERS +++ b/core/java/com/android/internal/os/OWNERS @@ -15,3 +15,6 @@ per-file *PowerStats* = file:/BATTERY_STATS_OWNERS # ANRs # Bug component : 158088 = per-file TimeoutRecord.java per-file TimeoutRecord.java = file:/PERFORMANCE_OWNERS + +# ApplicationSharedMemory +per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS diff --git a/core/java/com/android/internal/os/anr/OWNERS b/core/java/com/android/internal/os/anr/OWNERS index 9816752db891bd30120e465df03a7d1261833166..1ad642f78cde31e1c81616a3a8ea51f4e467b462 100644 --- a/core/java/com/android/internal/os/anr/OWNERS +++ b/core/java/com/android/internal/os/anr/OWNERS @@ -1,3 +1,2 @@ benmiles@google.com -gaillard@google.com mohamadmahmoud@google.com \ No newline at end of file diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig index c7117e977ee25a60fb9a5c35c799cd94f959fef4..07df24843d9d366ff1ab7d0c37a9fbdac6c8a016 100644 --- a/core/java/com/android/internal/os/flags.aconfig +++ b/core/java/com/android/internal/os/flags.aconfig @@ -27,4 +27,12 @@ flag { description: "If the debug store is enabled." bug: "314735374" is_fixed_read_only: true +} + +flag { + name: "application_shared_memory_enabled" + namespace: "system_performance" + description: "Whether ApplicationSharedMemory is enabled." + bug: "365575551" + is_fixed_read_only: true } \ No newline at end of file diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java index 12d326486e77cac89343c2a2c9867bf7a59936ae..032ac428371227c8ce8dee32f60d63d5028cc6e7 100644 --- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java +++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java @@ -3025,6 +3025,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) { this.splitCodePaths = splitCodePaths; + this.mSplits = null; // reset for paths changed if (splitCodePaths != null) { int size = splitCodePaths.length; for (int index = 0; index < size; index++) { diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index fbc058cc0330b54e65b6ca62bd61d29aea2ae245..b0e38e2564306bda6ba5b105c9749a00f5721189 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -122,18 +122,20 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto private final Lock mBackgroundServiceLock = new ReentrantLock(); private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor(); - public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) { + public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) + throws ServiceManager.ServiceNotFoundException { this(null, null, null, () -> {}, groups); } - public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) { + public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) + throws ServiceManager.ServiceNotFoundException { this(null, null, null, cacheUpdater, groups); } public PerfettoProtoLogImpl( @NonNull String viewerConfigFilePath, @NonNull Runnable cacheUpdater, - @NonNull IProtoLogGroup[] groups) { + @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException { this(viewerConfigFilePath, null, new ProtoLogViewerConfigReader(() -> { @@ -177,12 +179,14 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider, @Nullable ProtoLogViewerConfigReader viewerConfigReader, @NonNull Runnable cacheUpdater, - @NonNull IProtoLogGroup[] groups) { + @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException { this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater, groups, ProtoLogDataSource::new, - IProtoLogConfigurationService.Stub - .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE)) + android.tracing.Flags.clientSideProtoLogging() ? + IProtoLogConfigurationService.Stub.asInterface( + ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE) + ) : null ); } @@ -222,7 +226,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto if (android.tracing.Flags.clientSideProtoLogging()) { mProtoLogConfigurationService = configurationService; Objects.requireNonNull(mProtoLogConfigurationService, - "ServiceManager returned a null ProtoLog Configuration Service"); + "A null ProtoLog Configuration Service was provided!"); try { var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs(); diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java index bf77db7b6a33249c59da500ce8fbeb8dd80798ce..adf03fe5f77538c572d4569669f3f306c4dfdd74 100644 --- a/core/java/com/android/internal/protolog/ProtoLog.java +++ b/core/java/com/android/internal/protolog/ProtoLog.java @@ -16,6 +16,8 @@ package com.android.internal.protolog; +import android.os.ServiceManager; + import com.android.internal.protolog.common.IProtoLog; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogLevel; @@ -76,7 +78,11 @@ public class ProtoLog { groups = allGroups.toArray(new IProtoLogGroup[0]); } - sProtoLogInstance = new PerfettoProtoLogImpl(groups); + try { + sProtoLogInstance = new PerfettoProtoLogImpl(groups); + } catch (ServiceManager.ServiceNotFoundException e) { + throw new RuntimeException(e); + } } } else { sProtoLogInstance = new LogcatOnlyProtoLogImpl(); diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java index 7bdcf2d14b19c82168b93e830679c898f5473c6c..5d67534b1b44716038958a64ce715c5297fbdc08 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.os.ServiceManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -106,18 +107,23 @@ public class ProtoLogImpl { final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]); if (android.tracing.Flags.perfettoProtologTracing()) { - File f = new File(sViewerConfigPath); - if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) { - // 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 = - new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups); + try { + File f = new File(sViewerConfigPath); + if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) { + // 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 = + new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups); + } + } catch (ServiceManager.ServiceNotFoundException e) { + throw new RuntimeException(e); } } else { var protologImpl = new LegacyProtoLogImpl( diff --git a/core/java/com/android/internal/util/ScreenshotRequest.java b/core/java/com/android/internal/util/ScreenshotRequest.java index c8b7defb5276e84003e3a5f079c4e7d5b15d35f9..702e5e26ffc1c8cc7fb32938e467e2725849f312 100644 --- a/core/java/com/android/internal/util/ScreenshotRequest.java +++ b/core/java/com/android/internal/util/ScreenshotRequest.java @@ -33,6 +33,7 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import android.view.Display; import android.view.WindowManager; import java.util.Objects; @@ -53,11 +54,12 @@ public class ScreenshotRequest implements Parcelable { private final Bitmap mBitmap; private final Rect mBoundsInScreen; private final Insets mInsets; + private final int mDisplayId; private ScreenshotRequest( @WindowManager.ScreenshotType int type, @WindowManager.ScreenshotSource int source, ComponentName topComponent, int taskId, int userId, - Bitmap bitmap, Rect boundsInScreen, Insets insets) { + Bitmap bitmap, Rect boundsInScreen, Insets insets, int displayId) { mType = type; mSource = source; mTopComponent = topComponent; @@ -66,6 +68,7 @@ public class ScreenshotRequest implements Parcelable { mBitmap = bitmap; mBoundsInScreen = boundsInScreen; mInsets = insets; + mDisplayId = displayId; } ScreenshotRequest(Parcel in) { @@ -77,6 +80,7 @@ public class ScreenshotRequest implements Parcelable { mBitmap = HardwareBitmapBundler.bundleToHardwareBitmap(in.readTypedObject(Bundle.CREATOR)); mBoundsInScreen = in.readTypedObject(Rect.CREATOR); mInsets = in.readTypedObject(Insets.CREATOR); + mDisplayId = in.readInt(); } @WindowManager.ScreenshotType @@ -113,6 +117,10 @@ public class ScreenshotRequest implements Parcelable { return mTopComponent; } + public int getDisplayId() { + return mDisplayId; + } + @Override public int describeContents() { return 0; @@ -128,6 +136,7 @@ public class ScreenshotRequest implements Parcelable { dest.writeTypedObject(HardwareBitmapBundler.hardwareBitmapToBundle(mBitmap), 0); dest.writeTypedObject(mBoundsInScreen, 0); dest.writeTypedObject(mInsets, 0); + dest.writeInt(mDisplayId); } @NonNull @@ -161,6 +170,7 @@ public class ScreenshotRequest implements Parcelable { private int mTaskId = INVALID_TASK_ID; private int mUserId = USER_NULL; private ComponentName mTopComponent; + private int mDisplayId = Display.INVALID_DISPLAY; /** * Begin building a ScreenshotRequest. @@ -193,7 +203,7 @@ public class ScreenshotRequest implements Parcelable { } return new ScreenshotRequest(mType, mSource, mTopComponent, mTaskId, mUserId, mBitmap, - mBoundsInScreen, mInsets); + mBoundsInScreen, mInsets, mDisplayId); } /** @@ -255,6 +265,16 @@ public class ScreenshotRequest implements Parcelable { mInsets = insets; return this; } + + /** + * Set the display ID for this request. + * + * @param displayId see {@link Display} + */ + public Builder setDisplayId(int displayId) { + mDisplayId = displayId; + return this; + } } /** diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index e65b4b65945f8f6647c8980a8b65ec02df3fd0eb..c0a7383c9f06a26f1262d0ce9802ddd2a4ca4f19 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -16,14 +16,19 @@ package com.android.internal.widget; +import static java.lang.Float.NaN; + import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Insets; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; import android.graphics.Path; +import android.graphics.PorterDuff; import android.graphics.RectF; import android.graphics.Region; import android.hardware.input.InputManager; @@ -65,11 +70,14 @@ public class PointerLocationView extends View implements InputDeviceListener, private static final PointerState EMPTY_POINTER_STATE = new PointerState(); public static class PointerState { - // Trace of previous points. - private float[] mTraceX = new float[32]; - private float[] mTraceY = new float[32]; - private boolean[] mTraceCurrent = new boolean[32]; - private int mTraceCount; + private float mCurrentX = NaN; + private float mCurrentY = NaN; + private float mPreviousX = NaN; + private float mPreviousY = NaN; + private float mFirstX = NaN; + private float mFirstY = NaN; + private boolean mPreviousPointIsHistorical; + private boolean mCurrentPointIsHistorical; // True if the pointer is down. @UnsupportedAppUsage @@ -96,31 +104,20 @@ public class PointerLocationView extends View implements InputDeviceListener, public PointerState() { } - public void clearTrace() { - mTraceCount = 0; - } - - public void addTrace(float x, float y, boolean current) { - int traceCapacity = mTraceX.length; - if (mTraceCount == traceCapacity) { - traceCapacity *= 2; - float[] newTraceX = new float[traceCapacity]; - System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount); - mTraceX = newTraceX; - - float[] newTraceY = new float[traceCapacity]; - System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount); - mTraceY = newTraceY; - - boolean[] newTraceCurrent = new boolean[traceCapacity]; - System.arraycopy(mTraceCurrent, 0, newTraceCurrent, 0, mTraceCount); - mTraceCurrent= newTraceCurrent; + public void addTrace(float x, float y, boolean isHistorical) { + if (Float.isNaN(mFirstX)) { + mFirstX = x; + } + if (Float.isNaN(mFirstY)) { + mFirstY = y; } - mTraceX[mTraceCount] = x; - mTraceY[mTraceCount] = y; - mTraceCurrent[mTraceCount] = current; - mTraceCount += 1; + mPreviousX = mCurrentX; + mPreviousY = mCurrentY; + mCurrentX = x; + mCurrentY = y; + mPreviousPointIsHistorical = mCurrentPointIsHistorical; + mCurrentPointIsHistorical = isHistorical; } } @@ -149,6 +146,12 @@ public class PointerLocationView extends View implements InputDeviceListener, private final SparseArray mPointers = new SparseArray(); private final PointerCoords mTempCoords = new PointerCoords(); + // Draw the trace of all pointers in the current gesture in a separate layer + // that is not cleared on every frame so that we don't have to re-draw the + // entire trace on each frame. + private final Bitmap mTraceBitmap; + private final Canvas mTraceCanvas; + private final Region mSystemGestureExclusion = new Region(); private final Region mSystemGestureExclusionRejected = new Region(); private final Path mSystemGestureExclusionPath = new Path(); @@ -197,6 +200,10 @@ public class PointerLocationView extends View implements InputDeviceListener, mPathPaint.setARGB(255, 0, 96, 255); mPathPaint.setStyle(Paint.Style.STROKE); + mTraceBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics().widthPixels, + getResources().getDisplayMetrics().heightPixels, Bitmap.Config.ARGB_8888); + mTraceCanvas = new Canvas(mTraceBitmap); + configureDensityDependentFactors(); mSystemGestureExclusionPaint = new Paint(); @@ -256,7 +263,7 @@ public class PointerLocationView extends View implements InputDeviceListener, protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); mTextPaint.getFontMetricsInt(mTextMetrics); - mHeaderBottom = mHeaderPaddingTop-mTextMetrics.ascent+mTextMetrics.descent+2; + mHeaderBottom = mHeaderPaddingTop - mTextMetrics.ascent + mTextMetrics.descent + 2; if (false) { Log.i("foo", "Metrics: ascent=" + mTextMetrics.ascent + " descent=" + mTextMetrics.descent @@ -269,6 +276,7 @@ public class PointerLocationView extends View implements InputDeviceListener, // Draw an oval. When angle is 0 radians, orients the major axis vertically, // angles less than or greater than 0 radians rotate the major axis left or right. private RectF mReusableOvalRect = new RectF(); + private void drawOval(Canvas canvas, float x, float y, float major, float minor, float angle, Paint paint) { canvas.save(Canvas.MATRIX_SAVE_FLAG); @@ -285,6 +293,8 @@ public class PointerLocationView extends View implements InputDeviceListener, protected void onDraw(Canvas canvas) { final int NP = mPointers.size(); + canvas.drawBitmap(mTraceBitmap, 0, 0, null); + if (!mSystemGestureExclusion.isEmpty()) { mSystemGestureExclusionPath.reset(); mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath); @@ -303,32 +313,9 @@ public class PointerLocationView extends View implements InputDeviceListener, // Pointer trace. for (int p = 0; p < NP; p++) { final PointerState ps = mPointers.valueAt(p); + float lastX = ps.mCurrentX, lastY = ps.mCurrentY; - // Draw path. - final int N = ps.mTraceCount; - float lastX = 0, lastY = 0; - boolean haveLast = false; - boolean drawn = false; - mPaint.setARGB(255, 128, 255, 255); - for (int i=0; i < N; i++) { - float x = ps.mTraceX[i]; - float y = ps.mTraceY[i]; - if (Float.isNaN(x) || Float.isNaN(y)) { - haveLast = false; - continue; - } - if (haveLast) { - canvas.drawLine(lastX, lastY, x, y, mPathPaint); - final Paint paint = ps.mTraceCurrent[i - 1] ? mCurrentPointPaint : mPaint; - canvas.drawPoint(lastX, lastY, paint); - drawn = true; - } - lastX = x; - lastY = y; - haveLast = true; - } - - if (drawn) { + if (!Float.isNaN(lastX) && !Float.isNaN(lastY)) { // Draw velocity vector. mPaint.setARGB(255, 255, 64, 128); float xVel = ps.mXVelocity * (1000 / 60); @@ -353,7 +340,7 @@ public class PointerLocationView extends View implements InputDeviceListener, Math.max(getHeight(), getWidth()), mTargetPaint); // Draw current point. - int pressureLevel = (int)(ps.mCoords.pressure * 255); + int pressureLevel = (int) (ps.mCoords.pressure * 255); mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel); canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint); @@ -424,8 +411,7 @@ public class PointerLocationView extends View implements InputDeviceListener, .append(" / ").append(mMaxNumPointers) .toString(), 1, base, mTextPaint); - final int count = ps.mTraceCount; - if ((mCurDown && ps.mCurDown) || count == 0) { + if ((mCurDown && ps.mCurDown) || Float.isNaN(ps.mCurrentX)) { canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, mTextBackgroundPaint); canvas.drawText(mText.clear() @@ -437,8 +423,8 @@ public class PointerLocationView extends View implements InputDeviceListener, .append("Y: ").append(ps.mCoords.y, 1) .toString(), 1 + itemW * 2, base, mTextPaint); } else { - float dx = ps.mTraceX[count - 1] - ps.mTraceX[0]; - float dy = ps.mTraceY[count - 1] - ps.mTraceY[0]; + float dx = ps.mCurrentX - ps.mFirstX; + float dy = ps.mCurrentY - ps.mFirstY; canvas.drawRect(itemW, mHeaderPaddingTop, (itemW * 2) - 1, bottom, Math.abs(dx) < mVC.getScaledTouchSlop() ? mTextBackgroundPaint : mTextLevelPaint); @@ -565,9 +551,9 @@ public class PointerLocationView extends View implements InputDeviceListener, .append(" TouchMinor=").append(coords.touchMinor, 3) .append(" ToolMajor=").append(coords.toolMajor, 3) .append(" ToolMinor=").append(coords.toolMinor, 3) - .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1) + .append(" Orientation=").append((float) (coords.orientation * 180 / Math.PI), 1) .append("deg") - .append(" Tilt=").append((float)( + .append(" Tilt=").append((float) ( coords.getAxisValue(MotionEvent.AXIS_TILT) * 180 / Math.PI), 1) .append("deg") .append(" Distance=").append(coords.getAxisValue(MotionEvent.AXIS_DISTANCE), 1) @@ -598,6 +584,7 @@ public class PointerLocationView extends View implements InputDeviceListener, mCurNumPointers = 0; mMaxNumPointers = 0; mVelocity.clear(); + mTraceCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); if (mAltVelocity != null) { mAltVelocity.clear(); } @@ -646,7 +633,8 @@ public class PointerLocationView extends View implements InputDeviceListener, logCoords("Pointer", action, i, coords, id, event); } if (ps != null) { - ps.addTrace(coords.x, coords.y, false); + ps.addTrace(coords.x, coords.y, true); + updateDrawTrace(ps); } } } @@ -659,7 +647,8 @@ public class PointerLocationView extends View implements InputDeviceListener, logCoords("Pointer", action, i, coords, id, event); } if (ps != null) { - ps.addTrace(coords.x, coords.y, true); + ps.addTrace(coords.x, coords.y, false); + updateDrawTrace(ps); ps.mXVelocity = mVelocity.getXVelocity(id); ps.mYVelocity = mVelocity.getYVelocity(id); if (mAltVelocity != null) { @@ -702,13 +691,26 @@ public class PointerLocationView extends View implements InputDeviceListener, if (mActivePointerId == id) { mActivePointerId = event.getPointerId(index == 0 ? 1 : 0); } - ps.addTrace(Float.NaN, Float.NaN, false); + ps.addTrace(Float.NaN, Float.NaN, true); } } invalidate(); } + private void updateDrawTrace(PointerState ps) { + mPaint.setARGB(255, 128, 255, 255); + float x = ps.mCurrentX; + float y = ps.mCurrentY; + float lastX = ps.mPreviousX; + float lastY = ps.mPreviousY; + if (!Float.isNaN(x) && !Float.isNaN(y) && !Float.isNaN(lastX) && !Float.isNaN(lastY)) { + mTraceCanvas.drawLine(lastX, lastY, x, y, mPathPaint); + Paint paint = ps.mPreviousPointIsHistorical ? mPaint : mCurrentPointPaint; + mTraceCanvas.drawPoint(lastX, lastY, paint); + } + } + @Override public boolean onTouchEvent(MotionEvent event) { onPointerEvent(event); @@ -767,7 +769,7 @@ public class PointerLocationView extends View implements InputDeviceListener, return true; default: return KeyEvent.isGamepadButton(keyCode) - || KeyEvent.isModifierKey(keyCode); + || KeyEvent.isModifierKey(keyCode); } } @@ -887,7 +889,7 @@ public class PointerLocationView extends View implements InputDeviceListener, public FasterStringBuilder append(int value, int zeroPadWidth) { final boolean negative = value < 0; if (negative) { - value = - value; + value = -value; if (value < 0) { append("-2147483648"); return this; @@ -973,26 +975,27 @@ public class PointerLocationView extends View implements InputDeviceListener, private ISystemGestureExclusionListener mSystemGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { - @Override - public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion, - Region systemGestureExclusionUnrestricted) { - Region exclusion = Region.obtain(systemGestureExclusion); - Region rejected = Region.obtain(); - if (systemGestureExclusionUnrestricted != null) { - rejected.set(systemGestureExclusionUnrestricted); - rejected.op(exclusion, Region.Op.DIFFERENCE); - } - Handler handler = getHandler(); - if (handler != null) { - handler.post(() -> { - mSystemGestureExclusion.set(exclusion); - mSystemGestureExclusionRejected.set(rejected); - exclusion.recycle(); - invalidate(); - }); - } - } - }; + @Override + public void onSystemGestureExclusionChanged(int displayId, + Region systemGestureExclusion, + Region systemGestureExclusionUnrestricted) { + Region exclusion = Region.obtain(systemGestureExclusion); + Region rejected = Region.obtain(); + if (systemGestureExclusionUnrestricted != null) { + rejected.set(systemGestureExclusionUnrestricted); + rejected.op(exclusion, Region.Op.DIFFERENCE); + } + Handler handler = getHandler(); + if (handler != null) { + handler.post(() -> { + mSystemGestureExclusion.set(exclusion); + mSystemGestureExclusionRejected.set(rejected); + exclusion.recycle(); + invalidate(); + }); + } + } + }; @Override protected void onConfigurationChanged(Configuration newConfig) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 9a4ff8fc264fdf676634148f42c24055a39e58cc..9797d96623873fbf6409b0de528981971f37a96d 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -258,6 +258,7 @@ cc_library_shared_for_libandroid_runtime { "com_android_internal_content_om_OverlayConfig.cpp", "com_android_internal_content_om_OverlayManagerImpl.cpp", "com_android_internal_net_NetworkUtilsInternal.cpp", + "com_android_internal_os_ApplicationSharedMemory.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_DebugStore.cpp", "com_android_internal_os_FuseAppLoop.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 03b5143ac1f760ac637ae47e47c6a4a08e1add85..70a80b2bd4f1f9eed0607711198e71e356f587db 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -201,6 +201,7 @@ extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); +extern int register_com_android_internal_os_ApplicationSharedMemory(JNIEnv *env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_DebugStore(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); @@ -1516,6 +1517,7 @@ static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env } static const RegJNIRec gRegJNI[] = { + REG_JNI(register_com_android_internal_os_ApplicationSharedMemory), REG_JNI(register_com_android_internal_os_RuntimeInit), REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit), REG_JNI(register_android_os_SystemClock), diff --git a/core/jni/OWNERS b/core/jni/OWNERS index c0fe098c6a20d686de63eb0c04f011aab35517ad..af106235bd7722fde87471f888f78321857b659e 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -116,3 +116,6 @@ per-file android_os_PerformanceHintManager.cpp = file:/ADPF_OWNERS # IF Tools per-file android_tracing_Perfetto* = file:platform/development:/tools/winscope/OWNERS + +# ApplicationSharedMemory +per-file *ApplicationSharedMemory* = file:/PERFORMANCE_OWNERS diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp index 88e6fa3028ade4a64f902dd47d949a31776aa459..e99d89098a4078c67c5f460ca2095307b4c6f26e 100644 --- a/core/jni/android_os_SystemProperties.cpp +++ b/core/jni/android_os_SystemProperties.cpp @@ -34,8 +34,6 @@ #if defined(__BIONIC__) # include -#else -struct prop_info; #endif namespace android { @@ -46,7 +44,6 @@ using android::base::ParseBoolResult; template void ReadProperty(const prop_info* prop, Functor&& functor) { -#if defined(__BIONIC__) auto thunk = [](void* cookie, const char* /*name*/, const char* value, @@ -54,9 +51,6 @@ void ReadProperty(const prop_info* prop, Functor&& functor) std::forward(*static_cast(cookie))(value); }; __system_property_read_callback(prop, thunk, &functor); -#else - LOG(FATAL) << "fast property access supported only on device"; -#endif } template @@ -66,16 +60,11 @@ void ReadProperty(JNIEnv* env, jstring keyJ, Functor&& functor) if (!key.c_str()) { return; } -#if defined(__BIONIC__) const prop_info* prop = __system_property_find(key.c_str()); if (!prop) { return; } ReadProperty(prop, std::forward(functor)); -#else - std::forward(functor)( - android::base::GetProperty(key.c_str(), "").c_str()); -#endif } jstring SystemProperties_getSS(JNIEnv* env, jclass clazz, jstring keyJ, @@ -132,17 +121,12 @@ jboolean SystemProperties_get_boolean(JNIEnv *env, jclass, jstring keyJ, jlong SystemProperties_find(JNIEnv* env, jclass, jstring keyJ) { -#if defined(__BIONIC__) ScopedUtfChars key(env, keyJ); if (!key.c_str()) { return 0; } const prop_info* prop = __system_property_find(key.c_str()); return reinterpret_cast(prop); -#else - LOG(FATAL) << "fast property access supported only on device"; - __builtin_unreachable(); // Silence warning -#endif } jstring SystemProperties_getH(JNIEnv* env, jclass clazz, jlong propJ) @@ -198,11 +182,7 @@ void SystemProperties_set(JNIEnv *env, jobject clazz, jstring keyJ, // request" failures). errno = 0; bool success; -#if defined(__BIONIC__) success = !__system_property_set(key.c_str(), value_c_str); -#else - success = android::base::SetProperty(key.c_str(), value_c_str); -#endif if (!success) { if (errno != 0) { jniThrowExceptionFmt(env, "java/lang/RuntimeException", diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp index 5a444bb1d0ff3b5a3687ff6975b38ab2a023f5ba..c364451057bc2f1bdc24afdde04710f9b3287c16 100644 --- a/core/jni/android_util_XmlBlock.cpp +++ b/core/jni/android_util_XmlBlock.cpp @@ -83,7 +83,7 @@ static jlong android_content_XmlBlock_nativeCreateParseState(JNIEnv* env, jobjec return 0; } - ResXMLParser* st = new ResXMLParser(*osb); + ResXMLParser* st = new(std::nothrow) ResXMLParser(*osb); if (st == NULL) { jniThrowException(env, "java/lang/OutOfMemoryError", NULL); return 0; diff --git a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp new file mode 100644 index 0000000000000000000000000000000000000000..453e53974e0d707bd7fb4c0f2517815bf88c441a --- /dev/null +++ b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp @@ -0,0 +1,164 @@ +/* + * 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. + */ + +// See: ApplicationSharedMemory.md + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "core_jni_helpers.h" + +namespace { + +// Atomics should be safe to use across processes if they are lock free. +static_assert(std::atomic::is_always_lock_free == true, + "atomic is not always lock free"); + +// This is the data structure that is shared between processes. +// +// Tips for extending: +// - Atomics are safe for cross-process use as they are lock free, if they are accessed as +// individual values. +// - Consider multi-ABI systems, e.g. devices that support launching both 64-bit and 32-bit +// app processes. Use fixed-size types (e.g. `int64_t`) to ensure that the data structure is +// the same size across all ABIs. Avoid implicit assumptions about struct packing/padding. +class alignas(8) SharedMemory { // Ensure that `sizeof(SharedMemory)` is the same across 32-bit and + // 64-bit systems. +private: + volatile std::atomic latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis; + + // LINT.IfChange(invalid_network_time) + static constexpr int64_t INVALID_NETWORK_TIME = -1; + // LINT.ThenChange(frameworks/base/core/java/com/android/internal/os/ApplicationSharedMemory.java:invalid_network_time) + +public: + // Default constructor sets initial values + SharedMemory() + : latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(INVALID_NETWORK_TIME) {} + + int64_t getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis() const { + return latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis; + } + + void setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(int64_t offset) { + latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis = offset; + } +}; + +// Update the expected value when modifying the members of SharedMemory. +// The goal of this assertion is to ensure that the data structure is the same size across 32-bit +// and 64-bit systems. +static_assert(sizeof(SharedMemory) == 8, "Unexpected SharedMemory size"); + +static jint nativeCreate(JNIEnv* env, jclass) { + // Create anonymous shared memory region + int fd = ashmem_create_region("ApplicationSharedMemory", sizeof(SharedMemory)); + if (fd < 0) { + jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Failed to create ashmem: %s", + strerror(errno)); + } + return fd; +} + +static jlong nativeMap(JNIEnv* env, jclass, jint fd, jboolean isMutable) { + void* ptr = mmap(nullptr, sizeof(SharedMemory), isMutable ? PROT_READ | PROT_WRITE : PROT_READ, + MAP_SHARED, fd, 0); + if (ptr == MAP_FAILED) { + close(fd); + jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Failed to mmap shared memory: %s", + strerror(errno)); + } + + return reinterpret_cast(ptr); +} + +static void nativeInit(JNIEnv* env, jclass, jlong ptr) { + new (reinterpret_cast(ptr)) SharedMemory(); +} + +static void nativeUnmap(JNIEnv* env, jclass, jlong ptr) { + if (munmap(reinterpret_cast(ptr), sizeof(SharedMemory)) == -1) { + jniThrowExceptionFmt(env, "java/lang/RuntimeException", + "Failed to munmap shared memory: %s", strerror(errno)); + } +} + +static jint nativeDupAsReadOnly(JNIEnv* env, jclass, jint fd) { + // Duplicate file descriptor + fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (fd < 0) { + jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Failed to dup fd: %s", + strerror(errno)); + } + + // Set new file descriptor to read-only + if (ashmem_set_prot_region(fd, PROT_READ)) { + close(fd); + jniThrowExceptionFmt(env, "java/lang/RuntimeException", + "Failed to ashmem_set_prot_region: %s", strerror(errno)); + } + + return fd; +} + +static void nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(jlong ptr, + jlong offset) { + SharedMemory* sharedMemory = reinterpret_cast(ptr); + sharedMemory->setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(offset); +} + +static jlong nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(jlong ptr) { + SharedMemory* sharedMemory = reinterpret_cast(ptr); + return sharedMemory->getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(); +} + +static const JNINativeMethod gMethods[] = { + {"nativeCreate", "()I", (void*)nativeCreate}, + {"nativeMap", "(IZ)J", (void*)nativeMap}, + {"nativeInit", "(J)V", (void*)nativeInit}, + {"nativeUnmap", "(J)V", (void*)nativeUnmap}, + {"nativeDupAsReadOnly", "(I)I", (void*)nativeDupAsReadOnly}, + {"nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(JJ)V", + (void*)nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis}, + {"nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(J)J", + (void*)nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis}, +}; + +} // anonymous namespace + +namespace android { + +static const char kApplicationSharedMemoryClassName[] = + "com/android/internal/os/ApplicationSharedMemory"; +static jclass gApplicationSharedMemoryClass; + +int register_com_android_internal_os_ApplicationSharedMemory(JNIEnv* env) { + gApplicationSharedMemoryClass = + MakeGlobalRefOrDie(env, FindClassOrDie(env, kApplicationSharedMemoryClassName)); + RegisterMethodsOrDie(env, "com/android/internal/os/ApplicationSharedMemory", gMethods, + NELEM(gMethods)); + return JNI_OK; +} + +} // namespace android diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto index 78cf6f4645580a0ab87b089a9172fa93569f2df5..8e9f4478e89491aa2a1ed21f76d126cd129bfaab 100644 --- a/core/proto/android/app/appstartinfo.proto +++ b/core/proto/android/app/appstartinfo.proto @@ -41,4 +41,5 @@ message ApplicationStartInfoProto { optional AppStartLaunchMode launch_mode = 11; optional bool was_force_stopped = 12; optional int64 monotonic_creation_time_ms = 13; + optional int32 start_component = 14; } diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto index e7f0560612ccc350aefce22d17c4b33970336790..258832e3e7ff6e59181aaad613d4b02414f231d8 100644 --- a/core/proto/android/server/vibrator/vibratormanagerservice.proto +++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto @@ -157,10 +157,8 @@ message VibratorManagerServiceDumpProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; repeated int32 vibrator_ids = 1; optional VibrationProto current_vibration = 2; - optional bool is_vibrating = 3; optional int32 is_vibrator_controller_registered = 27; optional VibrationProto current_external_vibration = 4; - optional bool vibrator_under_external_control = 5; optional bool low_power_mode = 6; optional bool vibrate_on = 24; reserved 25; // prev keyboard_vibration_on @@ -183,4 +181,6 @@ message VibratorManagerServiceDumpProto { repeated VibrationProto previous_vibrations = 16; repeated VibrationParamProto previous_vibration_params = 28; reserved 17; // prev previous_external_vibrations + reserved 3; // prev is_vibrating, check current_vibration instead + reserved 5; // prev vibrator_under_external_control, check current_external_vibration instead } \ No newline at end of file diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index d0f1615e78e9b76876385a42719f80fc8c8d2679..a9626a650cf9a995003986b4aaed2cff1b06d4ec 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -2153,7 +2153,7 @@ "OK" "Deaktiver" "Få flere oplysninger" - "Tilpassede Android-notifikationer blev erstattet af forbedrede notifikationer i Android 12. Denne funktion viser foreslåede handlinger og svar samt organiserer dine notifikationer.\n\nForbedrede notifikationer kan få adgang til indhold i notifikationer, bl.a. personlige oplysninger såsom beskeder og navne på kontakter. Funktionen kan også afvise eller svare på notifikationer, f.eks. ved at besvare telefonopkald og justere Forstyr ikke." + "Adaptive Android-notifikationer blev erstattet af forbedrede notifikationer i Android 12. Denne funktion viser foreslåede handlinger og svar samt organiserer dine notifikationer.\n\nForbedrede notifikationer kan få adgang til indhold i notifikationer, bl.a. personlige oplysninger såsom beskeder og navne på kontakter. Funktionen kan også afvise eller svare på notifikationer, f.eks. ved at besvare telefonopkald og justere Forstyr ikke." "Notifikation med oplysninger om rutinetilstand" "Batterisparefunktion er aktiveret" "Reducerer batteriforbruget for at forlænge batteritiden" diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 14b512eb6d310d88f3694034c525b09ede1d0cad..f40fb93ca6a90192afeb3a1b745f421e2ed08be6 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -642,7 +642,7 @@ "એપને તમારા મીડિયા સંગ્રહમાંથી સ્થાનો વાંચવાની મંજૂરી આપે છે." "બાયોમેટ્રિક્સનો ઉપયોગ કરો" "બાયોમેટ્રિક્સ અથવા સ્ક્રીન લૉકનો ઉપયોગ કરો" - "તે તમે જ છો એ ચકાસો" + "આ તમે જ છો તેની ચકાસણી કરો" "આગળ વધવા માટે બાયોમેટ્રિકનો ઉપયોગ કરો" "ચાલુ રાખવા માટે તમારા બાયોમેટ્રિક ડેટા અથવા સ્ક્રીન લૉક સુવિધાનો ઉપયોગ કરો" "બાયોમેટ્રિક હાર્ડવેર ઉપલબ્ધ નથી" diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 0997b4b972de1a72dd78a5ce056a2441f8a09031..595b9baaa25464e196307ae4e0e54f1f8e8a1a76 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -642,7 +642,7 @@ "Қолданбаға медиамазмұн жинағынан геодеректерді оқуға мүмкіндік береді." "Биометриканы пайдалану" "Биометриканы немесе экран құлпын пайдалану" - "Бұл сіз екеніңізді растаңыз" + "Cіз екеніңізді растаңыз" "Жалғастыру үшін биометрикаңызды пайдаланыңыз." "Жалғастыру үшін биометриканы немесе экран құлпын пайдаланыңыз." "Биометрикалық жабдық жоқ" diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index c671f461b7de3d6a61f3a600e634fa43bd526ec4..61d322951879b6ec19dcc64e344b97fca4e6b094 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -644,7 +644,7 @@ "Umožňuje aplikácii čítať polohy zo zbierky médií." "Použiť biometrické údaje" "Použiť biometrické údaje alebo zámku obrazovky" - "Overenie, že ste to vy" + "Potvrďte, že ste to vy" "Ak chcete pokračovať, použite biometrický údaj" "Pokračujte použitím biometrických údajov alebo zámky obrazovky" "Biometrický hardvér nie je k dispozícii" @@ -2410,9 +2410,9 @@ "Rozloženie klávesnice je nastavené na jazyky %1$s, %2$s%3$s… Môžete to zmeniť klepnutím." "Fyzické klávesnice sú nakonfigurované" "Klávesnice si zobrazíte klepnutím" - "Súkromné" + "Súkromný" "Klon" - "Pracovné" + "Pracovný" "2. pracovný" "3. pracovný" "Testovací" diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index 1f8a2f7dfcc996434cd709e5daa634a641d22081..70732664c973a900117fd2f43487302a01902a3e 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -642,7 +642,7 @@ "మీ మీడియా సేకరణ నుండి లొకేషన్లను చదవడానికి యాప్‌ను అనుమతిస్తుంది." "బయోమెట్రిక్స్‌ను ఉపయోగించండి" "బయోమెట్రిక్స్‌ను లేదా స్క్రీన్ లాక్‌ను ఉపయోగించండి" - "ఇది మీరేనని వెరిఫై చేసుకోండి" + "ఈ చర్య చేస్తోంది మీరేనని వెరిఫై చేయండి" "కొనసాగించడానికి, మీ బయోమెట్రిక్‌ను ఉపయోగించండి" "కొనసాగించడానికి మీ బయోమెట్రిక్ లేదా స్క్రీన్ లాక్‌ను ఉపయోగించండి" "బయోమెట్రిక్ హార్డ్‌వేర్‌ అందుబాటులో లేదు" diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index a4d27527d48053856b441efcb969cc8dae913e2b..e76dacdf019114c11c474a12312bbecb3bbb4f59 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -2439,7 +2439,7 @@ "Hindi na makilala ang iyong face model. I-set up ulit ang Pag-unlock Gamit ang Mukha." "I-set up" "Huwag muna" - "Alarm para kay/sa %s" + "Alarm para kay %s" "Magpalit ng user" "I-mute" "I-tap para i-mute ang tunog" diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index f467f9264073f45f3a8f27b55396e7056bdf983a..ad6fc0de5cd5b7484484a45315b49bd333df97a4 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -642,7 +642,7 @@ "Uygulamanın medya koleksiyonunuzdaki konumları okumasına izin verir." "Biyometri kullan" "Biyometri veya ekran kilidi kullan" - "Siz olduğunuzu doğrulayın" + "Kimliğinizi doğrulayın" "Devam etmek için biyometri kullanın" "Devam etmek için biyometrik kimlik bilginizi veya ekran kilidinizi kullanın" "Biyometrik donanım kullanılamıyor" diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 660068d969ece4a8c82de468b3d716cd34792698..52d9e3bbc5cc80d6dc8915984b414c0047a28069 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -2439,7 +2439,7 @@ "آپ کے چہرے کا ماڈل مزید پہچانا نہیں جا سکتا۔ فیس اَن لاک کو دوبارہ سیٹ اپ کریں۔" "سیٹ اپ کریں" "ابھی نہیں" - "%s کیلئے الارم" + "‫%s کیلئے الارم" "صارف سوئچ کریں" "خاموش کریں" "آواز کو خاموش کرنے کے لیے تھپتھپائیں" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fe3d4f6b39bf3259ea9352994adc39c9502d253d..92c390656da582f07cb8113d6255d0d29eac7d1e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4380,7 +4380,7 @@ modes dimensions {@link config_minPercentageMultiWindowSupportWidth} the device supports to determine if the activity can be shown in multi windowing modes. --> - 0 + -1 0 + + 10 + 7 diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 69437b44fd6ebeb7136e239efe2ab0abd38b707f..9854030ed0d1ee8de1a1c320af174e7efb472586 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -287,6 +287,11 @@ + + + + 144dp + + 20dp + + 40dp + + 20dp @dimen/notification_small_icon_size diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 06b36b8f74afbdd6fedc048e1f934cfe7d204fd6..5f40a6c7eba434395b053462445ebe28f77e93f4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3467,6 +3467,7 @@ + @@ -3854,6 +3855,9 @@ + + + diff --git a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java index d34c91ee48bad50e2b70fd760222fa5f3eebb112..e81cdee940b4d71e46614d9cba027c77bcc67acd 100644 --- a/core/tests/GameManagerTests/src/android/app/GameManagerTests.java +++ b/core/tests/GameManagerTests/src/android/app/GameManagerTests.java @@ -22,7 +22,10 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; +import static org.junit.Assume.assumeNotNull; + import android.content.Context; +import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -47,6 +50,9 @@ public final class GameManagerTests { public void setUp() { mContext = getInstrumentation().getContext(); mGameManager = mContext.getSystemService(GameManager.class); + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) { + assumeNotNull(mGameManager); + } mPackageName = mContext.getPackageName(); // Reset the Game Mode for the test app, since it persists across invocations. diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index d98836f8ce202bfbb6ea734c9e4ed393da395c91..9821d433500f38f352637331caf109ed877ae598 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -25,6 +25,7 @@ filegroup { "BinderProxyCountingTestApp/src/**/*.java", "BinderProxyCountingTestService/src/**/*.java", "BinderDeathRecipientHelperApp/src/**/*.java", + "AppThatCallsBinderMethods/src/**/*.kt", ], visibility: ["//visibility:private"], } @@ -104,6 +105,7 @@ android_test { "mockito-target-extended-minus-junit4", "TestParameterInjector", "android.content.res.flags-aconfig-java", + "android.security.flags-aconfig-java", ], libs: [ @@ -143,6 +145,7 @@ android_test { ":BinderProxyCountingTestApp", ":BinderProxyCountingTestService", ":AppThatUsesAppOps", + ":AppThatCallsBinderMethods", ], } diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index b1f1e2c2db05280eb47ee36876707481fcae5f3f..05ab783c01bb01cd17b7d88ba7fb53051208469e 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -26,6 +26,7 @@

              This lock is used to ensure thread-safety when accessing and modifying the + * {@link #mCurrentDeviceState} field. It is acquired by both the binder thread (if + * {@link Flags#wlinfoOncreate()} is enabled) and the main thread (if + * {@link Flags#wlinfoOncreate()} is disabled) to prevent race conditions and + * ensure data consistency. + */ + private final Object mCurrentDeviceStateLock = new Object(); @NonNull private final RawFoldingFeatureProducer mRawFoldSupplier; - private final boolean mIsHalfOpenedSupported; - - private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() { + @NonNull + private final DeviceStateMapper mDeviceStateMapper; + + @VisibleForTesting + final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() { + // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the getData() + // implementation. See https://errorprone.info/bugpattern/GuardedBy for limitations. + @SuppressWarnings("GuardedBy") + @BinderThread // When Flags.wlinfoOncreate() is enabled. + @MainThread // When Flags.wlinfoOncreate() is disabled. @Override public void onDeviceStateChanged(@NonNull DeviceState state) { - mCurrentDeviceState = state; - mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer - .this::notifyFoldingFeatureChange); + synchronized (mCurrentDeviceStateLock) { + mCurrentDeviceState = state; + mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer.this + ::notifyFoldingFeatureChangeLocked); + } } }; @@ -95,41 +111,14 @@ public final class DeviceStateManagerFoldingFeatureProducer @NonNull RawFoldingFeatureProducer rawFoldSupplier, @NonNull DeviceStateManager deviceStateManager) { mRawFoldSupplier = rawFoldSupplier; - String[] deviceStatePosturePairs = context.getResources() - .getStringArray(R.array.config_device_state_postures); - mSupportedStates = deviceStateManager.getSupportedDeviceStates(); - boolean isHalfOpenedSupported = false; - for (String deviceStatePosturePair : deviceStatePosturePairs) { - String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); - if (deviceStatePostureMapping.length != 2) { - if (DEBUG) { - Log.e(TAG, "Malformed device state posture pair: " - + deviceStatePosturePair); - } - continue; - } + mDeviceStateMapper = + new DeviceStateMapper(context, deviceStateManager.getSupportedDeviceStates()); - int deviceState; - int posture; - try { - deviceState = Integer.parseInt(deviceStatePostureMapping[0]); - posture = Integer.parseInt(deviceStatePostureMapping[1]); - } catch (NumberFormatException e) { - if (DEBUG) { - Log.e(TAG, "Failed to parse device state or posture: " - + deviceStatePosturePair, - e); - } - continue; - } - isHalfOpenedSupported = isHalfOpenedSupported - || posture == CommonFoldingFeature.COMMON_STATE_HALF_OPENED; - mDeviceStateToPostureMap.put(deviceState, posture); - } - mIsHalfOpenedSupported = isHalfOpenedSupported; - if (mDeviceStateToPostureMap.size() > 0) { + if (!mDeviceStateMapper.isDeviceStateToPostureMapEmpty()) { + final Executor executor = + Flags.wlinfoOncreate() ? Runnable::run : context.getMainExecutor(); Objects.requireNonNull(deviceStateManager) - .registerCallback(context.getMainExecutor(), mDeviceStateCallback); + .registerCallback(executor, mDeviceStateCallback); } } @@ -137,50 +126,51 @@ public final class DeviceStateManagerFoldingFeatureProducer * Add a callback to mCallbacks if there is no device state. This callback will be run * once a device state is set. Otherwise,run the callback immediately. */ - private void runCallbackWhenValidState(@NonNull Consumer> callback, - String displayFeaturesString) { - if (isCurrentStateValid()) { - callback.accept(calculateFoldingFeature(displayFeaturesString)); + private void runCallbackWhenValidState(@NonNull DeviceState state, + @NonNull Consumer> callback, + @NonNull String displayFeaturesString) { + if (mDeviceStateMapper.isDeviceStateValid(state)) { + callback.accept(calculateFoldingFeature(state, displayFeaturesString)); } else { // This callback will be added to mCallbacks and removed once it runs once. - AcceptOnceConsumer> singleRunCallback = + final AcceptOnceConsumer> singleRunCallback = new AcceptOnceConsumer<>(this, callback); addDataChangedCallback(singleRunCallback); } } - /** - * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the - * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was - * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}. - * Returns a boolean value of whether the device state is valid. - */ - private boolean isCurrentStateValid() { - // If the device state is not found in the map, indexOfKey returns a negative number. - return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState.getIdentifier()) >= 0; - } - + // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the implementation of + // addDataChangedCallback(). See https://errorprone.info/bugpattern/GuardedBy for limitations. + @SuppressWarnings("GuardedBy") @Override protected void onListenersChanged() { super.onListenersChanged(); - if (hasListeners()) { - mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange); - } else { - mCurrentDeviceState = new DeviceState( - new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER, - "INVALID").build()); - mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange); + synchronized (mCurrentDeviceStateLock) { + if (hasListeners()) { + mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChangeLocked); + } else { + mCurrentDeviceState = INVALID_DEVICE_STATE; + mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChangeLocked); + } + } + } + + @NonNull + private DeviceState getCurrentDeviceState() { + synchronized (mCurrentDeviceStateLock) { + return mCurrentDeviceState; } } @NonNull @Override public Optional> getCurrentData() { - Optional displayFeaturesString = mRawFoldSupplier.getCurrentData(); - if (!isCurrentStateValid()) { + final Optional displayFeaturesString = mRawFoldSupplier.getCurrentData(); + final DeviceState state = getCurrentDeviceState(); + if (!mDeviceStateMapper.isDeviceStateValid(state) || displayFeaturesString.isEmpty()) { return Optional.empty(); } else { - return displayFeaturesString.map(this::calculateFoldingFeature); + return Optional.of(calculateFoldingFeature(state, displayFeaturesString.get())); } } @@ -191,7 +181,7 @@ public final class DeviceStateManagerFoldingFeatureProducer */ @NonNull public List getFoldsWithUnknownState() { - Optional optionalFoldingFeatureString = mRawFoldSupplier.getCurrentData(); + final Optional optionalFoldingFeatureString = mRawFoldSupplier.getCurrentData(); if (optionalFoldingFeatureString.isPresent()) { return CommonFoldingFeature.parseListFromString( @@ -201,7 +191,6 @@ public final class DeviceStateManagerFoldingFeatureProducer return Collections.emptyList(); } - /** * Returns the list of supported {@link DisplayFoldFeatureCommon} calculated from the * {@link DeviceStateManagerFoldingFeatureProducer}. @@ -218,16 +207,16 @@ public final class DeviceStateManagerFoldingFeatureProducer return foldFeatures; } - /** * Returns {@code true} if the device supports half-opened mode, {@code false} otherwise. */ public boolean isHalfOpenedSupported() { - return mIsHalfOpenedSupported; + return mDeviceStateMapper.mIsHalfOpenedSupported; } /** * Adds the data to the storeFeaturesConsumer when the data is ready. + * * @param storeFeaturesConsumer a consumer to collect the data when it is first available. */ @Override @@ -236,38 +225,123 @@ public final class DeviceStateManagerFoldingFeatureProducer if (TextUtils.isEmpty(displayFeaturesString)) { storeFeaturesConsumer.accept(new ArrayList<>()); } else { - runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString); + final DeviceState state = getCurrentDeviceState(); + runCallbackWhenValidState(state, storeFeaturesConsumer, displayFeaturesString); } }); } - private void notifyFoldingFeatureChange(String displayFeaturesString) { - if (!isCurrentStateValid()) { + @GuardedBy("mCurrentDeviceStateLock") + private void notifyFoldingFeatureChangeLocked(String displayFeaturesString) { + final DeviceState state = mCurrentDeviceState; + if (!mDeviceStateMapper.isDeviceStateValid(state)) { return; } if (TextUtils.isEmpty(displayFeaturesString)) { notifyDataChanged(new ArrayList<>()); } else { - notifyDataChanged(calculateFoldingFeature(displayFeaturesString)); + notifyDataChanged(calculateFoldingFeature(state, displayFeaturesString)); } } - private List calculateFoldingFeature(String displayFeaturesString) { - return parseListFromString(displayFeaturesString, currentHingeState()); + @NonNull + private List calculateFoldingFeature(@NonNull DeviceState deviceState, + @NonNull String displayFeaturesString) { + @CommonFoldingFeature.State + final int hingeState = mDeviceStateMapper.getHingeState(deviceState); + return parseListFromString(displayFeaturesString, hingeState); } - @CommonFoldingFeature.State - private int currentHingeState() { - @CommonFoldingFeature.State - int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState.getIdentifier(), - COMMON_STATE_UNKNOWN); + /** + * Internal class to map device states to corresponding postures. + * + *

              This class encapsulates the logic for mapping device states to postures. The mapping is + * immutable after initialization to ensure thread safety. + */ + private static class DeviceStateMapper { + /** + * Emulated device state + * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to + * {@link CommonFoldingFeature.State} map. + * + *

              This map must be immutable after initialization to ensure thread safety, as it may be + * accessed from multiple threads. Modifications should only occur during object + * construction. + */ + private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); + + /** + * The list of device states that are supported. + * + *

              This list must be immutable after initialization to ensure thread safety. + */ + @NonNull + private final List mSupportedStates; + + final boolean mIsHalfOpenedSupported; + + DeviceStateMapper(@NonNull Context context, @NonNull List supportedStates) { + mSupportedStates = supportedStates; + + final String[] deviceStatePosturePairs = context.getResources() + .getStringArray(R.array.config_device_state_postures); + boolean isHalfOpenedSupported = false; + for (String deviceStatePosturePair : deviceStatePosturePairs) { + final String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); + if (deviceStatePostureMapping.length != 2) { + if (DEBUG) { + Log.e(TAG, "Malformed device state posture pair: " + + deviceStatePosturePair); + } + continue; + } - if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) { - posture = mDeviceStateToPostureMap.get( - DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState, - mSupportedStates), COMMON_STATE_UNKNOWN); + final int deviceState; + final int posture; + try { + deviceState = Integer.parseInt(deviceStatePostureMapping[0]); + posture = Integer.parseInt(deviceStatePostureMapping[1]); + } catch (NumberFormatException e) { + if (DEBUG) { + Log.e(TAG, "Failed to parse device state or posture: " + + deviceStatePosturePair, + e); + } + continue; + } + isHalfOpenedSupported = isHalfOpenedSupported + || posture == CommonFoldingFeature.COMMON_STATE_HALF_OPENED; + mDeviceStateToPostureMap.put(deviceState, posture); + } + mIsHalfOpenedSupported = isHalfOpenedSupported; + } + + boolean isDeviceStateToPostureMapEmpty() { + return mDeviceStateToPostureMap.size() == 0; + } + + /** + * Validates if the provided deviceState exists in the {@link #mDeviceStateToPostureMap} + * which was initialized in the constructor of {@link DeviceStateMapper}. + * Returns a boolean value of whether the device state is valid. + */ + boolean isDeviceStateValid(@NonNull DeviceState deviceState) { + // If the device state is not found in the map, indexOfKey returns a negative number. + return mDeviceStateToPostureMap.indexOfKey(deviceState.getIdentifier()) >= 0; } - return posture; + @CommonFoldingFeature.State + int getHingeState(@NonNull DeviceState deviceState) { + @CommonFoldingFeature.State + final int posture = + mDeviceStateToPostureMap.get(deviceState.getIdentifier(), COMMON_STATE_UNKNOWN); + if (posture != CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) { + return posture; + } + + final int baseStateIdentifier = + DeviceStateUtil.calculateBaseStateIdentifier(deviceState, mSupportedStates); + return mDeviceStateToPostureMap.get(baseStateIdentifier, COMMON_STATE_UNKNOWN); + } } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index 74cce68f270b0a8f55c2bbd4d277f43175a97a26..dcc2d93060c97bb5c3589636c9602302cede44c3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -377,8 +377,16 @@ class TaskContainer { @Nullable TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { - return getContainer(container -> container.hasAppearedActivity(activityToken) - || container.hasPendingAppearedActivity(activityToken)); + // When the new activity is launched to the topmost TF because the source activity + // was in that TF, and the source activity is finished before resolving the new activity, + // we will try to see if the new activity match a rule with the split activities below. + // If matched, it can be reparented. + final TaskFragmentContainer taskFragmentContainer + = getContainer(container -> container.hasPendingAppearedActivity(activityToken)); + if (taskFragmentContainer != null) { + return taskFragmentContainer; + } + return getContainer(container -> container.hasAppearedActivity(activityToken)); } @Nullable diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp index bd430c0e610b28b504ee0c972d38e1b6deace1d8..09185ee203b8eceb1b60f75c65a956967af0d9ea 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp +++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp @@ -29,6 +29,7 @@ android_test { srcs: [ "**/*.java", + "**/*.kt", ], static_libs: [ @@ -41,6 +42,7 @@ android_test { "androidx.test.ext.junit", "flag-junit", "mockito-target-extended-minus-junit4", + "mockito-kotlin-nodeps", "truth", "testables", "platform-test-annotations", diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..90887a747a6ff683f95d6d4638d7126b2f55ddf8 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt @@ -0,0 +1,341 @@ +/* + * 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 androidx.window.common + +import android.content.Context +import android.content.res.Resources +import android.hardware.devicestate.DeviceState +import android.hardware.devicestate.DeviceStateManager +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.window.common.layout.CommonFoldingFeature +import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_FLAT +import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_HALF_OPENED +import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_NO_FOLDING_FEATURES +import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_UNKNOWN +import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE +import androidx.window.common.layout.DisplayFoldFeatureCommon +import androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED +import androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN +import com.android.internal.R +import com.android.window.flags.Flags +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import java.util.concurrent.Executor +import java.util.function.Consumer +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.kotlin.any +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify + +/** + * Test class for [DeviceStateManagerFoldingFeatureProducer]. + * + * Build/Install/Run: + * atest WMJetpackUnitTests:DeviceStateManagerFoldingFeatureProducerTest + */ +@RunWith(AndroidJUnit4::class) +class DeviceStateManagerFoldingFeatureProducerTest { + @get:Rule + val setFlagsRule: SetFlagsRule = SetFlagsRule() + + private val mMockDeviceStateManager = mock() + private val mMockResources = mock { + on { getStringArray(R.array.config_device_state_postures) } doReturn DEVICE_STATE_POSTURES + } + private val mMockContext = mock { + on { resources } doReturn mMockResources + } + private val mRawFoldSupplier = mock { + on { currentData } doReturn Optional.of(DISPLAY_FEATURES) + on { getData(any>()) } doAnswer { invocation -> + val callback = invocation.getArgument(0) as Consumer + callback.accept(DISPLAY_FEATURES) + } + } + + @Test + @DisableFlags(Flags.FLAG_WLINFO_ONCREATE) + fun testRegisterCallback_whenWlinfoOncreateIsDisabled_usesMainExecutor() { + DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + verify(mMockDeviceStateManager).registerCallback(eq(mMockContext.mainExecutor), any()) + } + + @Test + @EnableFlags(Flags.FLAG_WLINFO_ONCREATE) + fun testRegisterCallback_whenWlinfoOncreateIsEnabled_usesRunnableRun() { + val executorCaptor = ArgumentCaptor.forClass(Executor::class.java) + val runnable = mock() + + DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + verify(mMockDeviceStateManager).registerCallback(executorCaptor.capture(), any()) + executorCaptor.value.execute(runnable) + verify(runnable).run() + } + + @Test + fun testGetCurrentData_validCurrentState_returnsFoldingFeatureWithState() { + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED) + + val currentData = ffp.getCurrentData() + + assertThat(currentData).isPresent() + assertThat(currentData.get()).containsExactlyElementsIn(HALF_OPENED_FOLDING_FEATURES) + } + + @Test + fun testGetCurrentData_invalidCurrentState_returnsEmptyOptionalFoldingFeature() { + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + val currentData = ffp.getCurrentData() + + assertThat(currentData).isEmpty() + } + + @Test + fun testGetFoldsWithUnknownState_validFoldingFeature_returnsFoldingFeaturesWithUnknownState() { + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + val result = ffp.getFoldsWithUnknownState() + + assertThat(result).containsExactlyElementsIn(UNKNOWN_STATE_FOLDING_FEATURES) + } + + @Test + fun testGetFoldsWithUnknownState_emptyFoldingFeature_returnsEmptyList() { + mRawFoldSupplier.stub { + on { currentData } doReturn Optional.empty() + } + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + val result = ffp.getFoldsWithUnknownState() + + assertThat(result).isEmpty() + } + + @Test + fun testGetDisplayFeatures_validFoldingFeature_returnsDisplayFoldFeatures() { + mRawFoldSupplier.stub { + on { currentData } doReturn Optional.of(DISPLAY_FEATURES_HALF_OPENED_HINGE) + } + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + val result = ffp.displayFeatures + + assertThat(result).containsExactly( + DisplayFoldFeatureCommon( + DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN, + setOf(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED), + ), + ) + } + + @Test + fun testIsHalfOpenedSupported_withHalfOpenedPostures_returnsTrue() { + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + assertThat(ffp.isHalfOpenedSupported).isTrue() + } + + @Test + fun testIsHalfOpenedSupported_withEmptyPostures_returnsFalse() { + mMockResources.stub { + on { getStringArray(R.array.config_device_state_postures) } doReturn emptyArray() + } + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + assertThat(ffp.isHalfOpenedSupported).isFalse() + } + + @Test + fun testGetData_emptyDisplayFeaturesString_callsConsumerWithEmptyList() { + mRawFoldSupplier.stub { + on { getData(any>()) } doAnswer { invocation -> + val callback = invocation.getArgument(0) as Consumer + callback.accept("") + } + } + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + val storeFeaturesConsumer = mock>>() + + ffp.getData(storeFeaturesConsumer) + + verify(storeFeaturesConsumer).accept(emptyList()) + } + + @Test + fun testGetData_validState_callsConsumerWithFoldingFeatures() { + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED) + val storeFeaturesConsumer = mock>>() + + ffp.getData(storeFeaturesConsumer) + + verify(storeFeaturesConsumer).accept(HALF_OPENED_FOLDING_FEATURES) + } + + @Test + fun testGetData_invalidState_addsAcceptOnceConsumerToDataChangedCallback() { + val ffp = DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + val storeFeaturesConsumer = mock>>() + + ffp.getData(storeFeaturesConsumer) + + verify(storeFeaturesConsumer, never()).accept(any()) + ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED) + ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_OPENED) + verify(storeFeaturesConsumer).accept(HALF_OPENED_FOLDING_FEATURES) + } + + @Test + fun testDeviceStateMapper_malformedDeviceStatePosturePair_skipsPair() { + val malformedDeviceStatePostures = arrayOf( + // Missing the posture. + "0", + // Empty string. + "", + // Too many elements. + "0:1:2", + ) + mMockResources.stub { + on { getStringArray(R.array.config_device_state_postures) } doReturn + malformedDeviceStatePostures + } + + DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + verify(mMockDeviceStateManager, never()).registerCallback(any(), any()) + } + + @Test + fun testDeviceStateMapper_invalidNumberFormat_skipsPair() { + val invalidNumberFormatDeviceStatePostures = arrayOf("a:1", "0:b", "a:b", ":1") + mMockResources.stub { + on { getStringArray(R.array.config_device_state_postures) } doReturn + invalidNumberFormatDeviceStatePostures + } + + DeviceStateManagerFoldingFeatureProducer( + mMockContext, + mRawFoldSupplier, + mMockDeviceStateManager, + ) + + verify(mMockDeviceStateManager, never()).registerCallback(any(), any()) + } + + companion object { + // Supported device states configuration. + private enum class SupportedDeviceStates { + CLOSED, HALF_OPENED, OPENED, REAR_DISPLAY, CONCURRENT; + + override fun toString() = ordinal.toString() + + fun toDeviceState(): DeviceState = + DeviceState(DeviceState.Configuration.Builder(ordinal, name).build()) + } + + // Map of supported device states supplied by DeviceStateManager to WM Jetpack posture. + private val DEVICE_STATE_POSTURES = + arrayOf( + "${SupportedDeviceStates.CLOSED}:$COMMON_STATE_NO_FOLDING_FEATURES", + "${SupportedDeviceStates.HALF_OPENED}:$COMMON_STATE_HALF_OPENED", + "${SupportedDeviceStates.OPENED}:$COMMON_STATE_FLAT", + "${SupportedDeviceStates.REAR_DISPLAY}:$COMMON_STATE_NO_FOLDING_FEATURES", + "${SupportedDeviceStates.CONCURRENT}:$COMMON_STATE_USE_BASE_STATE", + ) + private val DEVICE_STATE_HALF_OPENED = SupportedDeviceStates.HALF_OPENED.toDeviceState() + private val DEVICE_STATE_OPENED = SupportedDeviceStates.OPENED.toDeviceState() + + // WindowsManager Jetpack display features. + private val DISPLAY_FEATURES = "fold-[1104,0,1104,1848]" + private val DISPLAY_FEATURES_HALF_OPENED_HINGE = "$DISPLAY_FEATURES-half-opened" + private val HALF_OPENED_FOLDING_FEATURES = CommonFoldingFeature.parseListFromString( + DISPLAY_FEATURES, + COMMON_STATE_HALF_OPENED, + ) + private val UNKNOWN_STATE_FOLDING_FEATURES = CommonFoldingFeature.parseListFromString( + DISPLAY_FEATURES, + COMMON_STATE_UNKNOWN, + ) + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 7fab371cb7902ad00adfea805758601f0ada1a3d..bc4916a607a374085e112a3e28a00a64c6e0a43b 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -535,7 +535,8 @@ public class TaskFragmentContainerTest { // container1. container2.setInfo(mTransaction, mInfo); - assertTrue(container2.hasActivity(mActivity.getActivityToken())); + assertTrue(container1.hasActivity(mActivity.getActivityToken())); + assertFalse(container2.hasActivity(mActivity.getActivityToken())); // When the pending appeared record is removed from container1, we respect the appeared // record in container2. container1.removePendingAppearedActivity(mActivity.getActivityToken()); diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 94809f2d258fcb407fb41962152c588e040ebd80..f8574294a3a2a8bc027818f742a6c3e0557b912b 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -147,8 +147,10 @@ java_library { java_library { name: "WindowManager-Shell-lite-proto", - srcs: ["src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto"], - + srcs: [ + "src/com/android/wm/shell/desktopmode/education/data/proto/**/*.proto", + "src/com/android/wm/shell/desktopmode/persistence/*.proto", + ], proto: { type: "lite", }, diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 526ccd55ce3da455d08477dab4d9a0a4bf188900..cf0a975b6c303e63703427d7c9b46236367a8551 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -121,6 +121,16 @@ flag { } } +flag { + name: "enable_shell_top_task_tracking" + namespace: "multitasking" + description: "Enables tracking top tasks from the shell" + bug: "342627272" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "enable_bubble_bar_in_persistent_task_bar" namespace: "multitasking" @@ -145,6 +155,13 @@ flag { bug: "363326492" } +flag { + name: "enable_flexible_two_app_split" + namespace: "multitasking" + description: "Enables only 2 app 90:10 split" + bug: "349828130" +} + flag { name: "enable_flexible_split" namespace: "multitasking" diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png index 991cdcf09416d79866057b535eb9f6f1ceb92cb1..c7b4c65b8c4b2c9c7f4cf3f9b83384bc541483ab 100644 Binary files a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png and b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/dark_portrait_bubbles_education.png differ diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png index 991cdcf09416d79866057b535eb9f6f1ceb92cb1..c7b4c65b8c4b2c9c7f4cf3f9b83384bc541483ab 100644 Binary files a/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png and b/libs/WindowManager/Shell/multivalentScreenshotTests/goldens/onDevice/phone/light_portrait_bubbles_education.png differ diff --git a/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml new file mode 100644 index 0000000000000000000000000000000000000000..07e5ac1a604ba006abbbb0d960308b2f7f1758bd --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..a12a7465895381bb3aa8b85dfdff4a1c101a98b9 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml new file mode 100644 index 0000000000000000000000000000000000000000..aadffb5a000382660918546128eae060b0b72a01 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml new file mode 100644 index 0000000000000000000000000000000000000000..e3c9a662671e2d3c45657a99b76f2891f67ef3f1 --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml index c0ff1922edc8fd70851fd07fa791745c46d160f3..1d1cdfa850402645acdc7c5139f0d8ef4d9a0824 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_handle.xml @@ -28,6 +28,8 @@ android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height" android:paddingVertical="16dp" android:paddingHorizontal="10dp" + android:screenReaderFocusable="true" + android:importantForAccessibility="yes" android:contentDescription="@string/handle_text" android:src="@drawable/decor_handle_dark" tools:tint="@color/desktop_mode_caption_handle_bar_dark" diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml index 7dcb3c237c51bcdec91200b13324d49fe531fa96..3dbf7542ac6e6894622538899870b8f0673052c5 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_header.xml @@ -31,14 +31,16 @@ android:orientation="horizontal" android:clickable="true" android:focusable="true" + android:contentDescription="@string/desktop_mode_app_header_chip_text" android:layout_marginStart="12dp"> @@ -90,6 +96,7 @@ + android:contentDescription="@string/app_icon_text" + android:importantForAccessibility="no"/> @@ -53,6 +55,7 @@ android:layout_marginBottom="76dp" android:gravity="center" android:fontFamily="google-sans-text" + android:importantForAccessibility="no" android:text="@string/desktop_mode_maximize_menu_maximize_text" android:textColor="?androidprv:attr/materialColorOnSurface" android:alpha="0"/> @@ -78,6 +81,8 @@ android:layout_height="@dimen/desktop_mode_maximize_menu_button_height" android:layout_marginRight="4dp" android:background="@drawable/desktop_mode_maximize_menu_button_background" + android:importantForAccessibility="yes" + android:contentDescription="@string/desktop_mode_maximize_menu_snap_left_button_text" android:stateListAnimator="@null"/>

            */ boolean shouldApplyFreeformTreatmentForCameraCompat() { - return Flags.cameraCompatForFreeform() && !isChangeEnabled(mActivityRecord, + return Flags.enableCameraCompatForDesktopWindowing() && !isChangeEnabled(mActivityRecord, OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); } diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java index 67bfd760512847ef9454d7166ecaf333710dbb04..5338c01666fe84338195964832cf480f60e9c3cb 100644 --- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java +++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java @@ -48,8 +48,9 @@ class AppCompatCameraPolicy { // without the need to restart the device. final boolean needsDisplayRotationCompatPolicy = wmService.mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); - final boolean needsCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform() - && DesktopModeHelper.canEnterDesktopMode(wmService.mContext); + final boolean needsCameraCompatFreeformPolicy = + Flags.enableCameraCompatForDesktopWindowing() + && DesktopModeHelper.canEnterDesktopMode(wmService.mContext); if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) { mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH); mActivityRefresher = new ActivityRefresher(wmService, wmService.mH); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 2259b5a5b08c943845b5145639a1afd6da44a9e1..515f148ac2ff498df4aa20131817e50ede95bbac 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -1120,7 +1120,9 @@ public class BackgroundActivityStartController { @Nullable Task targetTask, int launchFlags, int balCode, int callingUid, int realCallingUid, TaskDisplayArea preferredTaskDisplayArea) { // BAL Exception allowed in all cases - if (balCode == BAL_ALLOW_ALLOWLISTED_UID) { + if (balCode == BAL_ALLOW_ALLOWLISTED_UID + || (android.security.Flags.asmReintroduceGracePeriod() + && balCode == BAL_ALLOW_GRACE_PERIOD)) { return true; } @@ -1173,10 +1175,15 @@ public class BackgroundActivityStartController { ArrayList visibleTasks = displayArea.getVisibleTasks(); for (int i = 0; i < visibleTasks.size(); i++) { Task task = visibleTasks.get(i); - if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) { - bas.optedIn(task.getTopMostActivity()); - } else { + if (android.security.Flags.asmReintroduceGracePeriod()) { bas = checkTopActivityForAsm(task, callingUid, /*sourceRecord*/null, bas); + } else { + if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) { + bas.optedIn(task.getTopMostActivity()); + } else { + bas = checkTopActivityForAsm( + task, callingUid, /*sourceRecord*/null, bas); + } } } } diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java index e3232e08749e9b598bc6526b6faffc177a48e7fe..d6caa1a248b4dad0fd6dca4d6f5f238123ab4841 100644 --- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java +++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java @@ -124,7 +124,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa */ @VisibleForTesting boolean shouldApplyFreeformTreatmentForCameraCompat(@NonNull ActivityRecord activity) { - return Flags.cameraCompatForFreeform() && !activity.info.isChangeEnabled( + return Flags.enableCameraCompatForDesktopWindowing() && !activity.info.isChangeEnabled( ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 0fa1a2138e353b3e02186e64131c866f7667d720..e6f6215b5f7f5516de817aa6a2cb98b7a39b9f21 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2131,7 +2131,8 @@ public class DisplayPolicy { final DecorInsets.Info newInfo = mDecorInsets.mTmpInfo; final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh); final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh); - if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame) + final boolean sameConfigFrame = newInfo.mConfigFrame.equals(currentInfo.mConfigFrame); + if (sameConfigFrame && newInfo.mOverrideConfigFrame.equals(currentInfo.mOverrideConfigFrame)) { // Even if the config frame is not changed in current rotation, it may change the // insets in other rotations if the frame of insets source is changed. @@ -2155,7 +2156,7 @@ public class DisplayPolicy { } mDecorInsets.invalidate(); mDecorInsets.mInfoForRotation[rotation].set(newInfo); - return true; + return !sameConfigFrame; } DecorInsets.Info getDecorInsetsInfo(int rotation, int w, int h) { diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index e178203fed92237976b7d1602dd8636286b5b398..8cc2fd1bb88d8170837f820a21e5e66eecc7161f 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -132,15 +132,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } @Override - protected boolean isLeashReadyForDispatching(InsetsControlTarget target) { + protected boolean isLeashReadyForDispatching() { if (android.view.inputmethod.Flags.refactorInsetsController()) { final WindowState ws = mWindowContainer != null ? mWindowContainer.asWindowState() : null; final boolean isDrawn = ws != null && ws.isDrawn(); - return super.isLeashReadyForDispatching(target) + return super.isLeashReadyForDispatching() && mServerVisible && isDrawn && mGivenInsetsReady; } else { - return super.isLeashReadyForDispatching(target); + return super.isLeashReadyForDispatching(); } } @@ -636,7 +636,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { sb.append(", leash is: ").append(hasLeash ? "non-null" : "null"); if (!hasLeash) { sb.append(", control is: ").append(mControl != null ? "non-null" : "null"); - sb.append(", mIsLeashReadyForDispatching: ").append(mIsLeashReadyForDispatching); + sb.append(", mIsLeashInitialized: ").append(mIsLeashInitialized); } sb.append(", isImeLayeringTarget: "); sb.append(isImeLayeringTarget(mImeRequester, dcTarget)); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 232c3b62bcef7eab6ca47d819a038a445af9795a..dcf031953610dc0a5155adc44a7229f348e28892 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -188,9 +188,8 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal * the application did not handle. */ @Override - public KeyEvent dispatchUnhandledKey( - IBinder focusedToken, KeyEvent event, int policyFlags) { - return mService.mPolicy.dispatchUnhandledKey(focusedToken, event, policyFlags); + public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) { + return mService.mPolicy.interceptUnhandledKey(event, focusedToken); } /** Callback to get pointer layer. */ diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index ddbfd70ea4c41d0e701d8ae49d78ca7c2062f7f5..d7dc4597c50811ae38bece4823182096ea1a431f 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -222,7 +222,8 @@ final class InputMonitor { UserHandle clientUser) { final InputConsumerImpl existingConsumer = getInputConsumer(name); if (existingConsumer != null && existingConsumer.mClientUser.equals(clientUser)) { - throw new IllegalStateException("Existing input consumer found with name: " + name + destroyInputConsumer(existingConsumer.mToken); + Slog.w(TAG_WM, "Replacing existing input consumer found with name: " + name + ", display: " + mDisplayId + ", user: " + clientUser); } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index f0a4763796e380c7b8cd82e4799e30fd06033afe..4f8332a49750f0d6de69a4d3b9bf2ee04988d221 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -71,11 +71,12 @@ class InsetsSourceProvider { protected @Nullable WindowContainer mWindowContainer; protected @Nullable InsetsSourceControl mControl; protected @Nullable InsetsControlTarget mControlTarget; - protected boolean mIsLeashReadyForDispatching; + protected boolean mIsLeashInitialized; private final Rect mTmpRect = new Rect(); private final InsetsSourceControl mFakeControl; - private final Consumer mSetLeashPositionConsumer; + private final Point mPosition = new Point(); + private final Consumer mSetControlPositionConsumer; private @Nullable InsetsControlTarget mPendingControlTarget; private @Nullable InsetsControlTarget mFakeControlTarget; @@ -126,13 +127,14 @@ class InsetsSourceProvider { source.getId(), source.getType(), null /* leash */, false /* initialVisible */, new Point(), Insets.NONE); mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0; - mSetLeashPositionConsumer = t -> { - if (mControl != null) { - final SurfaceControl leash = mControl.getLeash(); - if (leash != null) { - final Point position = mControl.getSurfacePosition(); - t.setPosition(leash, position.x, position.y); - } + mSetControlPositionConsumer = t -> { + if (mControl == null || mControlTarget == null) { + return; + } + boolean changed = mControl.setSurfacePosition(mPosition.x, mPosition.y); + final SurfaceControl leash = mControl.getLeash(); + if (changed && leash != null) { + t.setPosition(leash, mPosition.x, mPosition.y); } if (mHasPendingPosition) { mHasPendingPosition = false; @@ -140,9 +142,22 @@ class InsetsSourceProvider { mStateController.notifyControlTargetChanged(mPendingControlTarget, this); } } + changed |= updateInsetsHint(); + if (changed) { + mStateController.notifyControlChanged(mControlTarget, this); + } }; } + private boolean updateInsetsHint() { + final Insets insetsHint = getInsetsHint(); + if (!mControl.getInsetsHint().equals(insetsHint)) { + mControl.setInsetsHint(insetsHint); + return true; + } + return false; + } + InsetsSource getSource() { return mSource; } @@ -363,26 +378,32 @@ class InsetsSourceProvider { } final boolean serverVisibleChanged = mServerVisible != isServerVisible; setServerVisible(isServerVisible); - updateInsetsControlPosition(windowState, serverVisibleChanged); - } - - void updateInsetsControlPosition(WindowState windowState) { - updateInsetsControlPosition(windowState, false); + final boolean positionChanged = updateInsetsControlPosition(windowState); + if (mControl != null && !positionChanged + // The insets hint would be updated if the position is changed. Here updates it for + // the possible change of the bounds or the server visibility. + && (updateInsetsHint() + || serverVisibleChanged + && android.view.inputmethod.Flags.refactorInsetsController())) { + // Only call notifyControlChanged here when the position is not changed. Otherwise, it + // is called or is scheduled to be called during updateInsetsControlPosition. + mStateController.notifyControlChanged(mControlTarget, this); + } } - private void updateInsetsControlPosition(WindowState windowState, - boolean serverVisibleChanged) { + /** + * @return {#code true} if the surface position of the control is changed. + */ + boolean updateInsetsControlPosition(WindowState windowState) { if (mControl == null) { - return; + return false; } - boolean changed = false; final Point position = getWindowFrameSurfacePosition(); - if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) { - changed = true; + if (!mPosition.equals(position)) { + mPosition.set(position.x, position.y); if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { - mHasPendingPosition = true; - windowState.applyWithNextDraw(mSetLeashPositionConsumer); + windowState.applyWithNextDraw(mSetControlPositionConsumer); } else { Transaction t = mWindowContainer.getSyncTransaction(); if (windowState != null) { @@ -399,20 +420,11 @@ class InsetsSourceProvider { } } } - mSetLeashPositionConsumer.accept(t); + mSetControlPositionConsumer.accept(t); } + return true; } - final Insets insetsHint = getInsetsHint(); - if (!mControl.getInsetsHint().equals(insetsHint)) { - mControl.setInsetsHint(insetsHint); - changed = true; - } - if (android.view.inputmethod.Flags.refactorInsetsController() && serverVisibleChanged) { - changed = true; - } - if (changed) { - mStateController.notifyControlChanged(mControlTarget, this); - } + return false; } private Point getWindowFrameSurfacePosition() { @@ -553,8 +565,8 @@ class InsetsSourceProvider { ANIMATION_TYPE_INSETS_CONTROL); // The leash was just created. We cannot dispatch it until its surface transaction is - // applied. Otherwise, the client's operation to the leash might be overwritten by us. - mIsLeashReadyForDispatching = false; + // committed. Otherwise, the client's operation to the leash might be overwritten by us. + mIsLeashInitialized = false; final SurfaceControl leash = mAdapter.mCapturedLeash; mControlTarget = target; @@ -590,7 +602,7 @@ class InsetsSourceProvider { * @param id Indicates which transaction is committed so that stale callbacks can be dropped. */ void onSurfaceTransactionCommitted(long id) { - if (mIsLeashReadyForDispatching) { + if (mIsLeashInitialized) { return; } if (mControl == null) { @@ -599,7 +611,7 @@ class InsetsSourceProvider { if (id != getSurfaceTransactionId(mControl.getLeash())) { return; } - mIsLeashReadyForDispatching = true; + mIsLeashInitialized = true; mStateController.notifySurfaceTransactionReady(this, 0, false); } @@ -650,9 +662,12 @@ class InsetsSourceProvider { mServerVisible, mClientVisible); } - protected boolean isLeashReadyForDispatching(InsetsControlTarget target) { - // If the target is not the control target, we are ready for dispatching a null-leash to it. - return target != mControlTarget || mIsLeashReadyForDispatching; + protected boolean isLeashReadyForDispatching() { + return isLeashInitialized(); + } + + boolean isLeashInitialized() { + return mIsLeashInitialized; } /** @@ -665,7 +680,7 @@ class InsetsSourceProvider { @Nullable InsetsSourceControl getControl(InsetsControlTarget target) { if (target == mControlTarget) { - if (!isLeashReadyForDispatching(target) && mControl != null) { + if (!isLeashReadyForDispatching() && mControl != null) { // The surface transaction of preparing leash is not applied yet. We don't send it // to the client in case that the client applies its transaction sooner than ours // that we could unexpectedly overwrite the surface state. @@ -690,7 +705,7 @@ class InsetsSourceProvider { */ @Nullable protected SurfaceControl getLeash(@NonNull InsetsControlTarget target) { - return target == mControlTarget && mIsLeashReadyForDispatching && mControl != null + return target == mControlTarget && mIsLeashInitialized && mControl != null ? mControl.getLeash() : null; } @@ -739,7 +754,7 @@ class InsetsSourceProvider { pw.println(); } pw.print(prefix); - pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching); + pw.print("mIsLeashInitialized="); pw.print(mIsLeashInitialized); pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition); pw.println(); if (mWindowContainer != null) { @@ -785,7 +800,7 @@ class InsetsSourceProvider { if (mAdapter != null && mAdapter.mCapturedLeash != null) { mAdapter.mCapturedLeash.dumpDebug(proto, CAPTURED_LEASH); } - proto.write(IS_LEASH_READY_FOR_DISPATCHING, mIsLeashReadyForDispatching); + proto.write(IS_LEASH_READY_FOR_DISPATCHING, isLeashReadyForDispatching()); proto.write(CLIENT_VISIBLE, mClientVisible); proto.write(SERVER_VISIBLE, mServerVisible); proto.write(SEAMLESS_ROTATING, mSeamlessRotating); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 3e39a45fa5f34887bf939c5e0cdd1a42bf1b14b1..03fadba30ae4d043eccc4a69835c5a162eab87c6 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -420,7 +420,7 @@ class InsetsStateController { final ArrayList providers = pendingControlMap.valueAt(i); for (int p = providers.size() - 1; p >= 0; p--) { final InsetsSourceProvider provider = providers.get(p); - if (provider.isLeashReadyForDispatching(target)) { + if (provider.isLeashInitialized() || provider.getControlTarget() != target) { // Stop waiting for this provider. providers.remove(p); } diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index 2394da91684d1d674b13d6084948c32925fb9628..4aa4f22ec14817cd3e1db966af4c407ed5ff2515 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -22,6 +22,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManagerInternal; import android.graphics.Rect; import android.os.Environment; +import android.os.Process; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; @@ -50,6 +51,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; import java.util.function.IntFunction; /** @@ -83,6 +87,12 @@ class LaunchParamsPersister { private PackageList mPackageList; + /** + * A map from user ID to the active {@link LoadingTask} when we're loading the launch params for + * that user. + */ + private final SparseArray mLoadingTaskMap = new SparseArray<>(); + /** * A dual layer map that first maps user ID to a secondary map, which maps component name (the * launching activity of tasks) to {@link PersistableLaunchParams} that stores launch metadata @@ -117,113 +127,33 @@ class LaunchParamsPersister { } void onUnlockUser(int userId) { - loadLaunchParams(userId); + if (mLoadingTaskMap.contains(userId)) { + Slog.e(TAG, "Duplicated onUnlockUser " + userId); + return; + } + + final LoadingTask task = new LoadingTask(userId); + mLoadingTaskMap.put(userId, task); + task.execute(); } void onCleanupUser(int userId) { + // There is no need to abort the task itself. Just let the loading task finish silently + // without modifying any state. + mLoadingTaskMap.remove(userId); mLaunchParamsMap.remove(userId); } - private void loadLaunchParams(int userId) { - final List filesToDelete = new ArrayList<>(); - final File launchParamsFolder = getLaunchParamFolder(userId); - if (!launchParamsFolder.isDirectory()) { - Slog.i(TAG, "Didn't find launch param folder for user " + userId); + private void waitAndMoveResultIfLoading(int userId) { + final LoadingTask task = mLoadingTaskMap.removeReturnOld(userId); + if (task == null) { return; } - - final Set packages = new ArraySet<>(mPackageList.getPackageNames()); - - final File[] paramsFiles = launchParamsFolder.listFiles(); - final ArrayMap map = - new ArrayMap<>(paramsFiles.length); - mLaunchParamsMap.put(userId, map); - - for (File paramsFile : paramsFiles) { - if (!paramsFile.isFile()) { - Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file."); - continue; - } - if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) { - Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName()); - filesToDelete.add(paramsFile); - continue; - } - String paramsFileName = paramsFile.getName(); - // Migrate all records from old separator to new separator. - final int oldSeparatorIndex = - paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR); - if (oldSeparatorIndex != -1) { - if (paramsFileName.indexOf( - OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) { - // Rare case. We have more than one old escaped component separator probably - // because this app uses underscore in their package name. We can't distinguish - // which one is the real separator so let's skip it. - filesToDelete.add(paramsFile); - continue; - } - paramsFileName = paramsFileName.replace( - OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR); - final File newFile = new File(launchParamsFolder, paramsFileName); - if (paramsFile.renameTo(newFile)) { - paramsFile = newFile; - } else { - // Rare case. For some reason we can't rename the file. Let's drop this record - // instead. - filesToDelete.add(paramsFile); - continue; - } - } - final String componentNameString = paramsFileName.substring( - 0 /* beginIndex */, - paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length()) - .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR); - final ComponentName name = ComponentName.unflattenFromString( - componentNameString); - if (name == null) { - Slog.w(TAG, "Unexpected file name: " + paramsFileName); - filesToDelete.add(paramsFile); - continue; - } - - if (!packages.contains(name.getPackageName())) { - // Rare case. PersisterQueue doesn't have a chance to remove files for removed - // packages last time. - filesToDelete.add(paramsFile); - continue; - } - - try (InputStream in = new FileInputStream(paramsFile)) { - final PersistableLaunchParams params = new PersistableLaunchParams(); - final TypedXmlPullParser parser = Xml.resolvePullParser(in); - int event; - while ((event = parser.next()) != XmlPullParser.END_DOCUMENT - && event != XmlPullParser.END_TAG) { - if (event != XmlPullParser.START_TAG) { - continue; - } - - final String tagName = parser.getName(); - if (!TAG_LAUNCH_PARAMS.equals(tagName)) { - Slog.w(TAG, "Unexpected tag name: " + tagName); - continue; - } - - params.restore(paramsFile, parser); - } - - map.put(name, params); - addComponentNameToLaunchParamAffinityMapIfNotNull( - name, params.mWindowLayoutAffinity); - } catch (Exception e) { - Slog.w(TAG, "Failed to restore launch params for " + name, e); - filesToDelete.add(paramsFile); - } - } - - if (!filesToDelete.isEmpty()) { - mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true); + final ArrayMap map = task.get(); + if (map == null) { + return; } + mLaunchParamsMap.put(userId, map); } void saveTask(Task task) { @@ -236,6 +166,7 @@ class LaunchParamsPersister { return; } final int userId = task.mUserId; + waitAndMoveResultIfLoading(userId); PersistableLaunchParams params; ArrayMap map = mLaunchParamsMap.get(userId); if (map == null) { @@ -297,6 +228,7 @@ class LaunchParamsPersister { void getLaunchParams(Task task, ActivityRecord activity, LaunchParams outParams) { final ComponentName name = task != null ? task.realActivity : activity.mActivityComponent; final int userId = task != null ? task.mUserId : activity.mUserId; + waitAndMoveResultIfLoading(userId); final String windowLayoutAffinity; if (task != null) { windowLayoutAffinity = task.mWindowLayoutAffinity; @@ -394,6 +326,137 @@ class LaunchParamsPersister { } } + private class LoadingTask + implements Callable> { + private final int mUserId; + private final FutureTask> mFutureTask; + + private LoadingTask(int userId) { + mUserId = userId; + mFutureTask = new FutureTask<>(this); + } + + private void execute() { + new Thread(mFutureTask).start(); + } + + private ArrayMap get() { + try { + return mFutureTask.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + Slog.e(TAG, "Failed to load launch params for user#" + mUserId, e); + return null; + } + } + + @Override + public ArrayMap call() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + + final List filesToDelete = new ArrayList<>(); + final File launchParamsFolder = getLaunchParamFolder(mUserId); + if (!launchParamsFolder.isDirectory()) { + Slog.i(TAG, "Didn't find launch param folder for user " + mUserId); + return null; + } + + final Set packages = new ArraySet<>(mPackageList.getPackageNames()); + + final File[] paramsFiles = launchParamsFolder.listFiles(); + final ArrayMap map = + new ArrayMap<>(paramsFiles.length); + + for (File paramsFile : paramsFiles) { + if (!paramsFile.isFile()) { + Slog.w(TAG, paramsFile.getAbsolutePath() + " is not a file."); + continue; + } + if (!paramsFile.getName().endsWith(LAUNCH_PARAMS_FILE_SUFFIX)) { + Slog.w(TAG, "Unexpected params file name: " + paramsFile.getName()); + filesToDelete.add(paramsFile); + continue; + } + String paramsFileName = paramsFile.getName(); + // Migrate all records from old separator to new separator. + final int oldSeparatorIndex = + paramsFileName.indexOf(OLD_ESCAPED_COMPONENT_SEPARATOR); + if (oldSeparatorIndex != -1) { + if (paramsFileName.indexOf( + OLD_ESCAPED_COMPONENT_SEPARATOR, oldSeparatorIndex + 1) != -1) { + // Rare case. We have more than one old escaped component separator probably + // because this app uses underscore in their package name. We can't + // distinguish which one is the real separator so let's skip it. + filesToDelete.add(paramsFile); + continue; + } + paramsFileName = paramsFileName.replace( + OLD_ESCAPED_COMPONENT_SEPARATOR, ESCAPED_COMPONENT_SEPARATOR); + final File newFile = new File(launchParamsFolder, paramsFileName); + if (paramsFile.renameTo(newFile)) { + paramsFile = newFile; + } else { + // Rare case. For some reason we can't rename the file. Let's drop this + // record instead. + filesToDelete.add(paramsFile); + continue; + } + } + final String componentNameString = paramsFileName.substring( + 0 /* beginIndex */, + paramsFileName.length() - LAUNCH_PARAMS_FILE_SUFFIX.length()) + .replace(ESCAPED_COMPONENT_SEPARATOR, ORIGINAL_COMPONENT_SEPARATOR); + final ComponentName name = ComponentName.unflattenFromString( + componentNameString); + if (name == null) { + Slog.w(TAG, "Unexpected file name: " + paramsFileName); + filesToDelete.add(paramsFile); + continue; + } + + if (!packages.contains(name.getPackageName())) { + // Rare case. PersisterQueue doesn't have a chance to remove files for removed + // packages last time. + filesToDelete.add(paramsFile); + continue; + } + + try (InputStream in = new FileInputStream(paramsFile)) { + final PersistableLaunchParams params = new PersistableLaunchParams(); + final TypedXmlPullParser parser = Xml.resolvePullParser(in); + int event; + while ((event = parser.next()) != XmlPullParser.END_DOCUMENT + && event != XmlPullParser.END_TAG) { + if (event != XmlPullParser.START_TAG) { + continue; + } + + final String tagName = parser.getName(); + if (!TAG_LAUNCH_PARAMS.equals(tagName)) { + Slog.w(TAG, "Unexpected tag name: " + tagName); + continue; + } + + params.restore(paramsFile, parser); + } + + map.put(name, params); + addComponentNameToLaunchParamAffinityMapIfNotNull( + name, params.mWindowLayoutAffinity); + } catch (Exception e) { + Slog.w(TAG, "Failed to restore launch params for " + name, e); + filesToDelete.add(paramsFile); + } + } + + if (!filesToDelete.isEmpty()) { + mPersisterQueue.addItem(new CleanUpComponentQueueItem(filesToDelete), true); + } + return map; + } + } + private class LaunchParamsWriteQueueItem implements PersisterQueue.WriteQueueItem { private final int mUserId; @@ -466,7 +529,7 @@ class LaunchParamsPersister { } } - private class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem { + private static class CleanUpComponentQueueItem implements PersisterQueue.WriteQueueItem { private final List mComponentFiles; private CleanUpComponentQueueItem(List componentFiles) { @@ -483,7 +546,7 @@ class LaunchParamsPersister { } } - private class PersistableLaunchParams { + private static class PersistableLaunchParams { private static final String ATTR_WINDOWING_MODE = "windowing_mode"; private static final String ATTR_DISPLAY_UNIQUE_ID = "display_unique_id"; private static final String ATTR_BOUNDS = "bounds"; diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 7aede8b4a06856ddaf646be2c12bc2d9ee3414b7..bf623b2e2105363b8fb524fd2b7dee03dbd981d0 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -24,6 +24,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -38,6 +40,7 @@ import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; +import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS_TRIM_TASKS; @@ -1493,12 +1496,20 @@ class RecentTasks { if (isExcludeFromRecents) { if (DEBUG_RECENTS_TRIM_TASKS) { Slog.d(TAG, - "\texcludeFromRecents=true, taskIndex = " + taskIndex - + ", isOnHomeDisplay: " + task.isOnHomeDisplay()); + "\texcludeFromRecents=true," + + " taskIndex: " + taskIndex + + " getTopVisibleActivity: " + task.getTopVisibleActivity() + + " isOnHomeDisplay: " + task.isOnHomeDisplay()); } // The Recents is only supported on default display now, we should only keep the // most recent task of home display. - return (task.isOnHomeDisplay() && taskIndex == 0); + boolean isMostRecentTask; + if (enableRefactorTaskThumbnail()) { + isMostRecentTask = task.getTopVisibleActivity() != null; + } else { + isMostRecentTask = taskIndex == 0; + } + return (task.isOnHomeDisplay() && isMostRecentTask); } } @@ -2031,10 +2042,15 @@ class RecentTasks { final boolean isOtherUndefinedMode = otherWindowingMode == WINDOWING_MODE_UNDEFINED; // An activity type and windowing mode is compatible if they are the exact same type/mode, - // or if one of the type/modes is undefined + // or if one of the type/modes is undefined. This is with the exception of + // freeform/fullscreen where both modes are assumed to be compatible with each other. final boolean isCompatibleType = activityType == otherActivityType || isUndefinedType || isOtherUndefinedType; final boolean isCompatibleMode = windowingMode == otherWindowingMode + || (windowingMode == WINDOWING_MODE_FREEFORM + && otherWindowingMode == WINDOWING_MODE_FULLSCREEN) + || (windowingMode == WINDOWING_MODE_FULLSCREEN + && otherWindowingMode == WINDOWING_MODE_FREEFORM) || isUndefinedMode || isOtherUndefinedMode; return isCompatibleType && isCompatibleMode; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 5550f3efaa3afef8e4d6e7b90b92f56a9d583c29..d295378a9b6562c6f66537f5c3929232dc4562a7 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -699,8 +699,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final WindowState win = mService.windowForClientLocked(this, window, false /* throwOnError */); if (win != null) { - ImeTracker.forLogging().onProgress(imeStatsToken, - ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + if (android.view.inputmethod.Flags.refactorInsetsController()) { + ImeTracker.forLogging().onProgress(imeStatsToken, + ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + } win.setRequestedVisibleTypes(requestedVisibleTypes); win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win, imeStatsToken); diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index 24fb20731c436555da53fb16e3a9e4095e13f061..896612d3d27aa24aa63a3494b6223e073e4b261b 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -68,7 +68,9 @@ public abstract class StartingData { * window. * Note this isn't equal to transition playing, the period should be * Sync finishNow -> Start transaction apply. + * @deprecated TODO(b/362347290): cleanup after fix ramp up */ + @Deprecated boolean mWaitForSyncTransactionCommit; /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 86bb75ab3f8c9de538c095a15ab29d6246f3a4f5..14f034bb84453991bcb56a9328edb5797f35717c 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -66,6 +66,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS; +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN; import static com.android.server.wm.ActivityRecord.State.PAUSED; import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESUMED; @@ -6177,6 +6178,8 @@ class Task extends TaskFragment { void maybeApplyLastRecentsAnimationTransaction() { if (mLastRecentsAnimationTransaction != null) { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "Applying last recents animation transaction."); final SurfaceControl.Transaction tx = getPendingTransaction(); if (mLastRecentsAnimationOverlay != null) { tx.reparent(mLastRecentsAnimationOverlay, mSurfaceControl); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 92953e5a504158ae686ddb61604e5141613c65f4..83e714d82dd2b1cc872539655a200fb0ecacf896 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -429,7 +429,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } final IBinder activityToken; - if (activity.getPid() == mOrganizerPid) { + if (activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid) { // We only pass the actual token if the activity belongs to the organizer process. activityToken = activity.token; } else { @@ -458,7 +458,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr change.setTaskFragmentToken(lastParentTfToken); } // Only pass the activity token to the client if it belongs to the same process. - if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) { + if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid + && nextFillTaskActivity.getUid() == mOrganizerUid) { change.setOtherActivityToken(nextFillTaskActivity.token); } return change; @@ -553,6 +554,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr "Replacing existing organizer currently unsupported"); } + if (pid <= 0) { + throw new IllegalStateException("Cannot register from invalid pid: " + pid); + } + if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) { return; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 0a47522f7df62d5222fb74b26eb9ab264debafba..2451ca87c3c789e816529ab9d495fbeb357efc1a 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1356,6 +1356,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mAtm.setLastResumedActivityUncheckLocked(ar, "transitionFinished"); } + + // Prevent spurious background app switches. + if (ar.mDisplayContent.mFocusedApp == ar) { + mController.mAtm.stopAppSwitches(); + } } continue; } @@ -1410,8 +1415,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (enterAutoPip) { mController.mAtm.getTaskChangeNotificationController().notifyTaskStackChanged(); } - // Prevent spurious background app switches. - mController.mAtm.stopAppSwitches(); // The end of transient launch may not reorder task, so make sure to compute the latest // task rank according to the current visibility. mController.mAtm.mRootWindowContainer.rankTaskLayers(); @@ -3498,14 +3501,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } boolean hasChanged() { + final boolean currVisible = mContainer.isVisibleRequested(); // the task including transient launch must promote to root task - if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0 - || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) { + if (currVisible && ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0 + || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0)) { return true; } // If it's invisible and hasn't changed visibility, always return false since even if // something changed, it wouldn't be a visible change. - final boolean currVisible = mContainer.isVisibleRequested(); if (currVisible == mVisible && !mVisible) return false; return currVisible != mVisible || mKnownConfigChanges != 0 diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 0a9cb1c38dabd55cbc59e3a1ff51dda271f48353..1c03ba571923474aead31a6c2ff7f805cc735d9c 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -4351,4 +4351,7 @@ class WindowContainer extends ConfigurationContainer< t.merge(mSyncTransaction); } + int getSyncTransactionCommitCallbackDepth() { + return mSyncTransactionCommitCallbackDepth; + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b8f47cce60055c144f363916752b2b038a82c173..942634704ff5d794f709ec07ffee01691debc6f2 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9007,7 +9007,9 @@ public class WindowManagerService extends IWindowManager.Stub final boolean isInputTargetNotFocused = mFocusedInputTarget != t && mFocusedInputTarget != null; - if (!isInputTargetNotFocused) { + final boolean isTouchOnFocusedDisplay = mFocusedInputTarget != null + && t.getDisplayId() == mFocusedInputTarget.getDisplayId(); + if (!(isInputTargetNotFocused && isTouchOnFocusedDisplay)) { return false; } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 476443aa20506dcf861f53c6ba3b09fd0c21fd2f..f35f2b30c5d423a885d18ae3054e6d6761682e20 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -799,7 +799,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } finally { if (deferTransitionReady) { - chain.mTransition.continueTransitionReady(); + if (chain.mTransition.isCollecting()) { + chain.mTransition.continueTransitionReady(); + } else { + Slog.wtf(TAG, "Too late, transition : " + chain.mTransition.getSyncId() + + " state: " + chain.mTransition.getState() + " is not collecting"); + } } mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); if (deferResume) { diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 32fe303b9e9024302afc3e38a6b18619a8a92ffa..2d9e8488a3e7f183186180383be73a1ff8918857 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -1543,6 +1543,11 @@ public class WindowProcessController extends ConfigurationContainer implements WindowManagerP boolean isReadyForDisplay() { final boolean parentAndClientVisible = !isParentWindowHidden() && mViewVisibility == View.VISIBLE; + // TODO(b/338426357): Remove this once the last target using legacy transitions is moved to + // shell transitions + if (!mTransitionController.isShellTransitionsEnabled()) { + return mHasSurface && isVisibleByPolicy() && !mDestroying + && ((parentAndClientVisible && mToken.isVisible()) + || isAnimating(TRANSITION | PARENTS)); + } return mHasSurface && isVisibleByPolicy() && !mDestroying && mToken.isVisible() && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS)); } @@ -3856,16 +3863,8 @@ class WindowState extends WindowContainer implements WindowManagerP } fillInsetsState(mLastReportedInsetsState, false /* copySources */); fillInsetsSourceControls(mLastReportedActiveControls, false /* copyControls */); - if (Flags.insetsControlChangedItem()) { - getProcess().scheduleClientTransactionItem(new WindowStateInsetsControlChangeItem( - mClient, mLastReportedInsetsState, mLastReportedActiveControls)); - } else { - try { - mClient.insetsControlChanged(mLastReportedInsetsState, mLastReportedActiveControls); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e); - } - } + getProcess().scheduleClientTransactionItem(new WindowStateInsetsControlChangeItem( + mClient, mLastReportedInsetsState, mLastReportedActiveControls)); } @Override diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 5cd117b512d4d5e7c59265e72c174ec1866b97b6..efca90217e835905adffcd65f692a0f25d48c14b 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include #include @@ -64,6 +65,7 @@ #include #include +#include #include #include "android_hardware_display_DisplayViewport.h" @@ -343,7 +345,7 @@ public: void setTouchpadRightClickZoneEnabled(bool enabled); void setInputDeviceEnabled(uint32_t deviceId, bool enabled); void setShowTouches(bool enabled); - void setInteractive(bool interactive); + void setNonInteractiveDisplays(const std::set& displayIds); void reloadCalibration(); void reloadPointerIcons(); void requestPointerCapture(const sp& windowToken, bool enabled); @@ -508,9 +510,11 @@ private: // Keycodes to be remapped. std::map keyRemapping{}; + + // Displays which are non-interactive. + std::set nonInteractiveDisplays; } mLocked GUARDED_BY(mLock); - std::atomic mInteractive; void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); @@ -524,12 +528,13 @@ private: void forEachPointerControllerLocked(std::function apply) REQUIRES(mLock); PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type); + bool isDisplayInteractive(ui::LogicalDisplayId displayId); static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } }; NativeInputManager::NativeInputManager(jobject serviceObj, const sp& looper) - : mLooper(looper), mInteractive(true) { + : mLooper(looper) { JNIEnv* env = jniEnv(); mServiceObj = env->NewGlobalRef(serviceObj); @@ -547,9 +552,13 @@ NativeInputManager::~NativeInputManager() { void NativeInputManager::dump(std::string& dump) { dump += "Input Manager State:\n"; - dump += StringPrintf(INDENT "Interactive: %s\n", toString(mInteractive.load())); { // acquire lock std::scoped_lock _l(mLock); + auto logicalDisplayIdToString = [](const ui::LogicalDisplayId& displayId) { + return std::to_string(displayId.val()); + }; + dump += StringPrintf(INDENT "Display not interactive: %s\n", + dumpSet(mLocked.nonInteractiveDisplays, streamableToString).c_str()); dump += StringPrintf(INDENT "System UI Lights Out: %s\n", toString(mLocked.systemUiLightsOut)); dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed); @@ -1476,8 +1485,10 @@ void NativeInputManager::requestPointerCapture(const sp& windowToken, b mInputManager->getDispatcher().requestPointerCapture(windowToken, enabled); } -void NativeInputManager::setInteractive(bool interactive) { - mInteractive = interactive; +void NativeInputManager::setNonInteractiveDisplays( + const std::set& displayIds) { + std::scoped_lock _l(mLock); + mLocked.nonInteractiveDisplays = displayIds; } void NativeInputManager::reloadCalibration() { @@ -1606,7 +1617,7 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent, // - Ignore untrusted events and pass them along. // - Ask the window manager what to do with normal events and trusted injected events. // - For normal events wake and brighten the screen if currently off or dim. - const bool interactive = mInteractive.load(); + const bool interactive = isDisplayInteractive(keyEvent.getDisplayId()); if (interactive) { policyFlags |= POLICY_FLAG_INTERACTIVE; } @@ -1644,7 +1655,7 @@ void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId disp // - No special filtering for injected events required at this time. // - Filter normal events based on screen state. // - For normal events brighten (but do not wake) the screen if currently dim. - const bool interactive = mInteractive.load(); + const bool interactive = isDisplayInteractive(displayId); if (interactive) { policyFlags |= POLICY_FLAG_INTERACTIVE; } @@ -1683,6 +1694,24 @@ void NativeInputManager::handleInterceptActions(jint wmActions, nsecs_t when, } } +bool NativeInputManager::isDisplayInteractive(ui::LogicalDisplayId displayId) { + // If an input event doesn't have an associated id, use the default display id + if (displayId == ui::LogicalDisplayId::INVALID) { + displayId = ui::LogicalDisplayId::DEFAULT; + } + + { // acquire lock + std::scoped_lock _l(mLock); + + auto it = mLocked.nonInteractiveDisplays.find(displayId); + if (it != mLocked.nonInteractiveDisplays.end()) { + return false; + } + } // release lock + + return true; +} + nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp& token, const KeyEvent& keyEvent, uint32_t policyFlags) { @@ -2372,10 +2401,17 @@ static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean en im->setShowTouches(enabled); } -static void nativeSetInteractive(JNIEnv* env, jobject nativeImplObj, jboolean interactive) { +static void nativeSetNonInteractiveDisplays(JNIEnv* env, jobject nativeImplObj, + jintArray displayIds) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setInteractive(interactive); + const std::vector displayIdsVec = getIntArray(env, displayIds); + std::set logicalDisplayIds; + for (int displayId : displayIdsVec) { + logicalDisplayIds.emplace(ui::LogicalDisplayId{displayId}); + } + + im->setNonInteractiveDisplays(logicalDisplayIds); } static void nativeReloadCalibration(JNIEnv* env, jobject nativeImplObj) { @@ -3021,7 +3057,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeSetShouldNotifyTouchpadHardwareState}, {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled}, {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches}, - {"setInteractive", "(Z)V", (void*)nativeSetInteractive}, + {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays}, {"reloadCalibration", "()V", (void*)nativeReloadCalibration}, {"vibrate", "(I[J[III)V", (void*)nativeVibrate}, {"vibrateCombined", "(I[JLandroid/util/SparseArray;II)V", (void*)nativeVibrateCombined}, diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp index cf9611468fabf32044da1c3ac695c353536d6011..2836d46b0f1a8f9cece02d26c0eb2fc549bc49ef 100644 --- a/services/core/jni/com_android_server_utils_AnrTimer.cpp +++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include @@ -26,6 +28,7 @@ #include #include #include +#include #define LOG_TAG "AnrTimerService" #define ATRACE_TAG ATRACE_TAG_ACTIVITY_MANAGER @@ -33,8 +36,8 @@ #include #include -#include "android_runtime/AndroidRuntime.h" -#include "core_jni_helpers.h" +#include +#include #include #include @@ -109,30 +112,334 @@ bool processExists(pid_t pid) { // Return the name of the process whose pid is the input. If the process does not exist, the // name will "notfound". std::string getProcessName(pid_t pid) { - char buffer[PATH_MAX]; - snprintf(buffer, sizeof(buffer), "/proc/%d/cmdline", pid); - int fd = ::open(buffer, O_RDONLY); - if (fd >= 0) { - size_t pos = 0; - ssize_t result; - while (pos < sizeof(buffer)-1) { - result = ::read(fd, buffer + pos, (sizeof(buffer) - pos) - 1); - if (result <= 0) { - break; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); + FILE* cmdline = fopen(path, "r"); + if (cmdline != nullptr) { + char name[PATH_MAX]; + char const *retval = fgets(name, sizeof(name), cmdline); + fclose(cmdline); + if (retval == nullptr) { + return std::string("unknown"); + } else { + return std::string(name); + } + } else { + return std::string("notfound"); + } +} + +/** + * Three wrappers of the trace utilities, which hard-code the timer track. + */ +void traceBegin(const char* msg, int cookie) { + ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, msg, cookie); +} + +void traceEnd(int cookie) { + ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie); +} + +void traceEvent(const char* msg) { + ATRACE_INSTANT_FOR_TRACK(ANR_TIMER_TRACK, msg); +} + +/** + * This class captures tracing information for processes tracked by an AnrTimer. A user can + * configure tracing to have the AnrTimerService emit extra information for watched processes. + * singleton. + * + * The tracing configuration has two components: process selection and an optional early action. + * + * Processes are selected in one of three ways: + * 1. A list of numeric linux process IDs. + * 2. A regular expression, matched against process names. + * 3. The keyword "all", to trace every process that uses an AnrTimer. + * Perfetto trace events are always emitted for every operation on a traced process. + * + * An early action occurs before the scheduled timeout. The early timeout is specified as a + * percentage (integer value in the range 0:100) of the programmed timeout. The AnrTimer will + * execute the early action at the early timeout. The early action may terminate the timer. + * + * There is one early action: + * 1. Expire - consider the AnrTimer expired and report it to the upper layers. + */ +class AnrTimerTracer { + public: + // Actions that can be taken when an early timer expires. + enum EarlyAction { + // Take no action. This is the value used when tracing is disabled. + None, + // Trace the timer but take no other action. + Trace, + // Report timer expiration to the upper layers. This is terminal, in that + Expire, + }; + + // The trace information for a single timer. + struct TraceConfig { + bool enabled = false; + EarlyAction action = None; + int earlyTimeout = 0; + }; + + AnrTimerTracer() { + AutoMutex _l(lock_); + resetLocked(); + } + + // Return the TraceConfig for a process. + TraceConfig getConfig(int pid) { + AutoMutex _l(lock_); + // The most likely situation: no tracing is configured. + if (!config_.enabled) return {}; + if (matchAllPids_) return config_; + if (watched_.contains(pid)) return config_; + if (!matchNames_) return {}; + if (matchedPids_.contains(pid)) return config_; + if (unmatchedPids_.contains(pid)) return {}; + std::string proc_name = getProcessName(pid); + bool matched = regexec(®ex_, proc_name.c_str(), 0, 0, 0) == 0; + if (matched) { + matchedPids_.insert(pid); + return config_; + } else { + unmatchedPids_.insert(pid); + return {}; + } + } + + // Set the trace configuration. The input is a string that contains key/value pairs of the + // form "key=value". Pairs are separated by spaces. The function returns a string status. + // On success, the normalized config is returned. On failure, the configuration reset the + // result contains an error message. As a special case, an empty set of configs, or a + // config that contains only the keyword "show", will do nothing except return the current + // configuration. On any error, all tracing is disabled. + std::pair setConfig(const std::vector& config) { + AutoMutex _l(lock_); + if (config.size() == 0) { + // Implicit "show" + return { true, currentConfigLocked() }; + } else if (config.size() == 1) { + // Process the one-word commands + const char* s = config[0].c_str(); + if (strcmp(s, "show") == 0) { + return { true, currentConfigLocked() }; + } else if (strcmp(s, "off") == 0) { + resetLocked(); + return { true, currentConfigLocked() }; + } else if (strcmp(s, "help") == 0) { + return { true, help() }; } + } else if (config.size() > 2) { + return { false, "unexpected values in config" }; + } + + // Barring an error in the remaining specification list, tracing will be enabled. + resetLocked(); + // Fetch the process specification. This must be the first configuration entry. + { + auto result = setTracedProcess(config[0]); + if (!result.first) return result; } - ::close(fd); - if (result >= 0) { - buffer[pos] = 0; + // Process optional actions. + if (config.size() > 1) { + auto result = setTracedAction(config[1]); + if (!result.first) return result; + } + + // Accept the result. + config_.enabled = true; + return { true, currentConfigLocked() }; + } + + private: + // Identify the processes to be traced. + std::pair setTracedProcess(std::string config) { + const char* s = config.c_str(); + const char* word = nullptr; + + if (strcmp(s, "pid=all") == 0) { + matchAllPids_ = true; + } else if ((word = startsWith(s, "pid=")) != nullptr) { + int p; + int n; + while (sscanf(word, "%d%n", &p, &n) == 1) { + watched_.insert(p); + word += n; + if (*word == ',') word++; + } + if (*word != 0) { + return { false, "invalid pid list" }; + } + config_.action = Trace; + } else if ((word = startsWith(s, "name=")) != nullptr) { + if (matchNames_) { + regfree(®ex_); + matchNames_ = false; + } + if (regcomp(®ex_, word, REG_EXTENDED) != 0) { + return { false, "invalid regex" }; + } + matchNames_ = true; + namePattern_ = word; + config_.action = Trace; } else { - snprintf(buffer, sizeof(buffer), "err: %s", strerror(errno)); + return { false, "no process specified" }; } - } else { - snprintf(buffer, sizeof(buffer), "notfound"); + return { true, "" }; + } + + // Set the action to be taken on a traced process. The incoming default action is Trace; + // this method may overwrite that action. + std::pair setTracedAction(std::string config) { + const char* s = config.c_str(); + const char* word = nullptr; + if (sscanf(s, "expire=%d", &config_.earlyTimeout) == 1) { + if (config_.earlyTimeout < 0) { + return { false, "invalid expire timeout" }; + } + config_.action = Expire; + } else { + return { false, std::string("cannot parse action ") + s }; + } + return { true, "" }; + } + + // Return the string value of an action. + static const char* toString(EarlyAction action) { + switch (action) { + case None: return "none"; + case Trace: return "trace"; + case Expire: return "expire"; + } + return "unknown"; + } + + // Return the action represented by the string. + static EarlyAction fromString(const char* action) { + if (strcmp(action, "expire") == 0) return Expire; + return None; } - return std::string(buffer); -} + + // Return the help message. This has everything except the invocation command. + static std::string help() { + static const char* msg = + "help show this message\n" + "show report the current configuration\n" + "off clear the current configuration, turning off all tracing\n" + "spec... configure tracing according to the specification list\n" + " action= what to do when a split timer expires\n" + " expire expire the timer to the upper levels\n" + " event generate extra trace events\n" + " pid=[,] watch the processes in the pid list\n" + " pid=all watch every process in the system\n" + " name= watch the processes whose name matches the regex\n"; + return msg; + } + + // A small convenience function for parsing. If the haystack starts with the needle and the + // haystack has at least one more character following, return a pointer to the following + // character. Otherwise return null. + static const char* startsWith(const char* haystack, const char* needle) { + if (strncmp(haystack, needle, strlen(needle)) == 0 && strlen(haystack) + strlen(needle)) { + return haystack + strlen(needle); + } + return nullptr; + } + + // Return the currently watched pids. The lock must be held. + std::string watchedPidsLocked() const { + if (watched_.size() == 0) return "none"; + bool first = true; + std::string result = ""; + for (auto i = watched_.cbegin(); i != watched_.cend(); i++) { + if (first) { + result += StringPrintf("%d", *i); + } else { + result += StringPrintf(",%d", *i); + } + } + return result; + } + + // Return the current configuration, in a form that can be consumed by setConfig(). + std::string currentConfigLocked() const { + if (!config_.enabled) return "off"; + std::string result; + if (matchAllPids_) { + result = "pid=all"; + } else if (matchNames_) { + result = StringPrintf("name=\"%s\"", namePattern_.c_str()); + } else { + result = std::string("pid=") + watchedPidsLocked(); + } + switch (config_.action) { + case None: + break; + case Trace: + // The default action is Trace + break; + case Expire: + result += StringPrintf(" %s=%d", toString(config_.action), config_.earlyTimeout); + break; + } + return result; + } + + // Reset the current configuration. + void resetLocked() { + if (!config_.enabled) return; + + config_.enabled = false; + config_.earlyTimeout = 0; + config_.action = {}; + matchAllPids_ = false; + watched_.clear(); + if (matchNames_) regfree(®ex_); + matchNames_ = false; + namePattern_ = ""; + matchedPids_.clear(); + unmatchedPids_.clear(); + } + + // The lock for all operations + mutable Mutex lock_; + + // The current tracing information, when a process matches. + TraceConfig config_; + + // A short-hand flag that causes all processes to be tracing without the overhead of + // searching any of the maps. + bool matchAllPids_; + + // A set of process IDs that should be traced. This is updated directly in setConfig() + // and only includes pids that were explicitly called out in the configuration. + std::set watched_; + + // Name mapping is a relatively expensive operation, since the process name must be fetched + // from the /proc file system and then a regex must be evaluated. However, name mapping is + // useful to ensure processes are traced at the moment they start. To make this faster, a + // process's name is matched only once, and the result is stored in the matchedPids_ or + // unmatchedPids_ set, as appropriate. This can lead to confusion if a process changes its + // name after it starts. + + // The global flag that enables name matching. If this is disabled then all name matching + // is disabled. + bool matchNames_; + + // The regular expression that matches processes to be traced. This is saved for logging. + std::string namePattern_; + + // The compiled regular expression. + regex_t regex_; + + // The set of all pids that whose process names match (or do not match) the name regex. + // There is one set for pids that match and one set for pids that do not match. + std::set matchedPids_; + std::set unmatchedPids_; +}; /** * This class encapsulates the anr timer service. The service manages a list of individual @@ -177,7 +484,7 @@ class AnrTimerService { * traditional void* and Java object pointer. The remaining parameters are * configuration options. */ - AnrTimerService(char const* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*, + AnrTimerService(const char* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*, bool extend, bool freeze); // Delete the service and clean up memory. @@ -211,6 +518,11 @@ class AnrTimerService { // Release a timer. The timer must be in the expired list. bool release(timer_id_t); + // Configure a trace specification to trace selected timers. See AnrTimerTracer for details. + static std::pair trace(const std::vector& spec) { + return tracer_.setConfig(spec); + } + // Return the Java object associated with this instance. jweak jtimer() const { return notifierObject_; @@ -221,7 +533,7 @@ class AnrTimerService { private: // The service cannot be copied. - AnrTimerService(AnrTimerService const&) = delete; + AnrTimerService(const AnrTimerService&) = delete; // Insert a timer into the running list. The lock must be held by the caller. void insertLocked(const Timer&); @@ -230,7 +542,7 @@ class AnrTimerService { Timer removeLocked(timer_id_t timerId); // Add a timer to the expired list. - void addExpiredLocked(Timer const&); + void addExpiredLocked(const Timer&); // Scrub the expired list by removing all entries for non-existent processes. The expired // lock must be held by the caller. @@ -240,10 +552,10 @@ class AnrTimerService { static const char* statusString(Status); // The name of this service, for logging. - std::string const label_; + const std::string label_; // The callback that is invoked when a timer expires. - notifier_t const notifier_; + const notifier_t notifier_; // The two cookies passed to the notifier. void* notifierCookie_; @@ -289,8 +601,13 @@ class AnrTimerService { // The clock used by this AnrTimerService. Ticker *ticker_; + + // The global tracing specification. + static AnrTimerTracer tracer_; }; +AnrTimerTracer AnrTimerService::tracer_; + class AnrTimerService::ProcessStats { public: nsecs_t cpu_time; @@ -337,14 +654,23 @@ class AnrTimerService::ProcessStats { class AnrTimerService::Timer { public: // A unique ID assigned when the Timer is created. - timer_id_t const id; + const timer_id_t id; // The creation parameters. The timeout is the original, relative timeout. - int const pid; - int const uid; - nsecs_t const timeout; - bool const extend; - bool const freeze; + const int pid; + const int uid; + const nsecs_t timeout; + // True if the timer may be extended. + const bool extend; + // True if process should be frozen when its timer expires. + const bool freeze; + // This is a percentage between 0 and 100. If it is non-zero then timer will fire at + // timeout*split/100, and the EarlyAction will be invoked. The timer may continue running + // or may expire, depending on the action. Thus, this value "splits" the timeout into two + // pieces. + const int split; + // The action to take if split (above) is non-zero, when the timer reaches the split point. + const AnrTimerTracer::EarlyAction action; // The state of this timer. Status status; @@ -355,6 +681,9 @@ class AnrTimerService::Timer { // The scheduled timeout. This is an absolute time. It may be extended. nsecs_t scheduled; + // True if this timer is split and in its second half + bool splitting; + // True if this timer has been extended. bool extended; @@ -367,22 +696,10 @@ class AnrTimerService::Timer { // The default constructor is used to create timers that are Invalid, representing the "not // found" condition when a collection is searched. - Timer() : - id(NOTIMER), - pid(0), - uid(0), - timeout(0), - extend(false), - freeze(false), - status(Invalid), - started(0), - scheduled(0), - extended(false), - frozen(false) { - } + Timer() : Timer(NOTIMER) { } - // This constructor creates a timer with the specified id. This can be used as the argument - // to find(). + // This constructor creates a timer with the specified id and everything else set to + // "empty". This can be used as the argument to find(). Timer(timer_id_t id) : id(id), pid(0), @@ -390,29 +707,37 @@ class AnrTimerService::Timer { timeout(0), extend(false), freeze(false), + split(0), + action(AnrTimerTracer::None), status(Invalid), started(0), scheduled(0), + splitting(false), extended(false), frozen(false) { } // Create a new timer. This starts the timer. - Timer(int pid, int uid, nsecs_t timeout, bool extend, bool freeze) : + Timer(int pid, int uid, nsecs_t timeout, bool extend, bool freeze, + AnrTimerTracer::TraceConfig trace) : id(nextId()), pid(pid), uid(uid), timeout(timeout), extend(extend), freeze(pid != 0 && freeze), + split(trace.earlyTimeout), + action(trace.action), status(Running), started(now()), - scheduled(started + timeout), + scheduled(started + (split > 0 ? (timeout*split)/100 : timeout)), + splitting(false), extended(false), frozen(false) { if (extend && pid != 0) { initial.fill(pid); } + // A zero-pid is odd but it means the upper layers will never ANR the process. Freezing // is always disabled. (It won't work anyway, but disabling it avoids error messages.) ALOGI_IF(DEBUG_ERROR && pid == 0, "error: zero-pid %s", toString().c_str()); @@ -434,6 +759,23 @@ class AnrTimerService::Timer { // returns false if the timer is eligible for extension. If the function returns false, the // scheduled time is updated. bool expire() { + if (split > 0 && !splitting) { + scheduled = started + timeout; + splitting = true; + event("split"); + switch (action) { + case AnrTimerTracer::None: + case AnrTimerTracer::Trace: + break; + case AnrTimerTracer::Expire: + status = Expired; + maybeFreezeProcess(); + event("expire"); + break; + } + return status == Expired; + } + nsecs_t extension = 0; if (extend && !extended) { // Only one extension is permitted. @@ -525,15 +867,15 @@ class AnrTimerService::Timer { char tag[PATH_MAX]; snprintf(tag, sizeof(tag), "freeze(pid=%d,uid=%d)", pid, uid); - ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, tag, cookie); + traceBegin(tag, cookie); if (SetProcessProfiles(uid, pid, {"Frozen"})) { ALOGI("freeze %s name=%s", toString().c_str(), getName().c_str()); frozen = true; - ATRACE_ASYNC_FOR_TRACK_BEGIN(ANR_TIMER_TRACK, "frozen", cookie+1); + traceBegin("frozen", cookie+1); } else { ALOGE("error: freezing %s name=%s error=%s", toString().c_str(), getName().c_str(), strerror(errno)); - ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie); + traceEnd(cookie); } } @@ -543,7 +885,7 @@ class AnrTimerService::Timer { // See maybeFreezeProcess for an explanation of the cookie. const uint32_t cookie = id << 1; - ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie+1); + traceEnd(cookie+1); if (SetProcessProfiles(uid, pid, {"Unfrozen"})) { ALOGI("unfreeze %s name=%s", toString().c_str(), getName().c_str()); frozen = false; @@ -551,7 +893,7 @@ class AnrTimerService::Timer { ALOGE("error: unfreezing %s name=%s error=%s", toString().c_str(), getName().c_str(), strerror(errno)); } - ATRACE_ASYNC_FOR_TRACK_END(ANR_TIMER_TRACK, cookie); + traceEnd(cookie); } // Get the next free ID. NOTIMER is never returned. @@ -564,12 +906,17 @@ class AnrTimerService::Timer { } // Log an event, non-verbose. - void event(char const* tag) { + void event(const char* tag) { event(tag, false); } // Log an event, guarded by the debug flag. - void event(char const* tag, bool verbose) { + void event(const char* tag, bool verbose) { + if (action != AnrTimerTracer::None) { + char msg[PATH_MAX]; + snprintf(msg, sizeof(msg), "%s(pid=%d)", tag, pid); + traceEvent(msg); + } if (verbose) { char name[PATH_MAX]; ALOGI_IF(DEBUG_TIMER, "event %s %s name=%s", @@ -594,12 +941,12 @@ class AnrTimerService::Ticker { struct Entry { const nsecs_t scheduled; const timer_id_t id; - AnrTimerService* const service; + AnrTimerService* service; Entry(nsecs_t scheduled, timer_id_t id, AnrTimerService* service) : scheduled(scheduled), id(id), service(service) {}; - bool operator<(const Entry &r) const { + bool operator<(const Entry& r) const { return scheduled == r.scheduled ? id < r.id : scheduled < r.scheduled; } }; @@ -664,7 +1011,7 @@ class AnrTimerService::Ticker { } // Remove every timer associated with the service. - void remove(AnrTimerService const* service) { + void remove(const AnrTimerService* service) { AutoMutex _l(lock_); timer_id_t front = headTimerId(); for (auto i = running_.begin(); i != running_.end(); ) { @@ -746,7 +1093,7 @@ class AnrTimerService::Ticker { // scheduled expiration time of the first entry. void restartLocked() { if (!running_.empty()) { - Entry const x = *(running_.cbegin()); + const Entry x = *(running_.cbegin()); nsecs_t delay = x.scheduled - now(); // Force a minimum timeout of 10ns. if (delay < 10) delay = 10; @@ -807,7 +1154,7 @@ class AnrTimerService::Ticker { std::atomic AnrTimerService::Ticker::idGen_; -AnrTimerService::AnrTimerService(char const* label, notifier_t notifier, void* cookie, +AnrTimerService::AnrTimerService(const char* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker* ticker, bool extend, bool freeze) : label_(label), notifier_(notifier), @@ -841,7 +1188,7 @@ const char* AnrTimerService::statusString(Status s) { AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid, nsecs_t timeout) { AutoMutex _l(lock_); - Timer t(pid, uid, timeout, extend_, freeze_); + Timer t(pid, uid, timeout, extend_, freeze_, tracer_.getConfig(pid)); insertLocked(t); t.start(); counters_.started++; @@ -918,7 +1265,7 @@ bool AnrTimerService::release(timer_id_t id) { return okay; } -void AnrTimerService::addExpiredLocked(Timer const& timer) { +void AnrTimerService::addExpiredLocked(const Timer& timer) { scrubExpiredLocked(); expired_.insert(timer); } @@ -1077,7 +1424,7 @@ jlong anrTimerCreate(JNIEnv* env, jobject jtimer, jstring jname, ScopedUtfChars name(env, jname); jobject timer = env->NewWeakGlobalRef(jtimer); AnrTimerService* service = new AnrTimerService(name.c_str(), - anrNotify, &gAnrArgs, timer, gAnrArgs.ticker, extend, freeze); + anrNotify, &gAnrArgs, timer, gAnrArgs.ticker, extend, freeze); return reinterpret_cast(service); } @@ -1122,6 +1469,19 @@ jboolean anrTimerRelease(JNIEnv* env, jclass, jlong ptr, jint timerId) { return toService(ptr)->release(timerId); } +jstring anrTimerTrace(JNIEnv* env, jclass, jobjectArray jconfig) { + if (!nativeSupportEnabled) return nullptr; + std::vector config; + const jsize jlen = jconfig == nullptr ? 0 : env->GetArrayLength(jconfig); + for (size_t i = 0; i < jlen; i++) { + jstring je = static_cast(env->GetObjectArrayElement(jconfig, i)); + ScopedUtfChars e(env, je); + config.push_back(e.c_str()); + } + auto r = AnrTimerService::trace(config); + return env->NewStringUTF(r.second.c_str()); +} + jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) { if (!nativeSupportEnabled) return nullptr; std::vector stats = toService(ptr)->getDump(); @@ -1134,22 +1494,23 @@ jobjectArray anrTimerDump(JNIEnv *env, jclass, jlong ptr) { } static const JNINativeMethod methods[] = { - {"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported}, - {"nativeAnrTimerCreate", "(Ljava/lang/String;ZZ)J", (void*) anrTimerCreate}, - {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose}, - {"nativeAnrTimerStart", "(JIIJ)I", (void*) anrTimerStart}, - {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel}, - {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept}, - {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard}, - {"nativeAnrTimerRelease", "(JI)Z", (void*) anrTimerRelease}, - {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump}, + {"nativeAnrTimerSupported", "()Z", (void*) anrTimerSupported}, + {"nativeAnrTimerCreate", "(Ljava/lang/String;ZZ)J", (void*) anrTimerCreate}, + {"nativeAnrTimerClose", "(J)I", (void*) anrTimerClose}, + {"nativeAnrTimerStart", "(JIIJ)I", (void*) anrTimerStart}, + {"nativeAnrTimerCancel", "(JI)Z", (void*) anrTimerCancel}, + {"nativeAnrTimerAccept", "(JI)Z", (void*) anrTimerAccept}, + {"nativeAnrTimerDiscard", "(JI)Z", (void*) anrTimerDiscard}, + {"nativeAnrTimerRelease", "(JI)Z", (void*) anrTimerRelease}, + {"nativeAnrTimerTrace", "([Ljava/lang/String;)Ljava/lang/String;", (void*) anrTimerTrace}, + {"nativeAnrTimerDump", "(J)[Ljava/lang/String;", (void*) anrTimerDump}, }; } // anonymous namespace int register_android_server_utils_AnrTimer(JNIEnv* env) { - static const char *className = "com/android/server/utils/AnrTimer"; + static const char* className = "com/android/server/utils/AnrTimer"; jniRegisterNativeMethods(env, className, methods, NELEM(methods)); nativeSupportEnabled = NATIVE_SUPPORT; diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 0eafb59bdeac8fa76c0599ab2de799b8b5d65c0c..a07facf794238342ce5d5c12ffde31c72e8d7ca7 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -512,8 +512,6 @@ - - diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 355b0ab15a62c336769653816eb60644e95eba7d..5309263ed87cbd6a1eddb98bf85e930c7e555cb3 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -91,8 +91,6 @@ package com.android.server.display.config { public class ComprehensiveBrightnessMap { ctor public ComprehensiveBrightnessMap(); method @NonNull public final java.util.List getBrightnessPoint(); - method public String getInterpolation(); - method public void setInterpolation(String); } public class Density { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6314b8564c543735223b0e6d539590ae3fcf0f82..4e89b85305d1daaed0323cdb3cbef82ccd8fe46b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3515,6 +3515,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return true; } + @GuardedBy("getLockObject()") + private boolean maybeMigrateResetPasswordTokenLocked(String backupId) { + if (!Flags.resetPasswordWithTokenCoexistence()) { + Slog.i(LOG_TAG, "ResetPasswordWithToken not migrated because coexistence " + + "support is not enabled."); + return false; + } + if (mOwners.isResetPasswordWithTokenMigrated()) { + // TODO(b/359187209): Remove log after Flags.resetPasswordWithTokenCoexistence full + // rollout. + Slog.v(LOG_TAG, "ResetPasswordWithToken was previously migrated to " + + "policy engine."); + return false; + } + + Slog.i(LOG_TAG, "Migrating ResetPasswordWithToken to policy engine"); + + // Create backup if none exists + mDevicePolicyEngine.createBackup(backupId); + try { + iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> { + int userId = enforcingAdmin.getUserId(); + DevicePolicyData policy = getUserData(userId); + if (policy.mPasswordTokenHandle != 0) { + Slog.i(LOG_TAG, "Setting RESET_PASSWORD_TOKEN policy"); + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.RESET_PASSWORD_TOKEN, + enforcingAdmin, + new LongPolicyValue(policy.mPasswordTokenHandle), + userId); + } + }); + } catch (Exception e) { + Slog.wtf(LOG_TAG, + "Failed to migrate ResetPasswordWithToken to policy engine", e); + } + + Slog.i(LOG_TAG, "Marking ResetPasswordWithToken migration complete"); + mOwners.markResetPasswordWithTokenMigrated(); + return true; + } + /** Register callbacks for statsd pulled atoms. */ private void registerStatsCallbacks() { final StatsManager statsManager = mContext.getSystemService(StatsManager.class); @@ -4110,8 +4152,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private void checkAllUsersAreAffiliatedWithDevice() { - Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(), - "operation not allowed when device has unaffiliated users"); + synchronized (getLockObject()) { + Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(), + "operation not allowed when device has unaffiliated users"); + } } @Override @@ -6395,7 +6439,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void lockNow(int flags, String callerPackageName, boolean parent) { CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.lockNowCoexistence()) { caller = getCallerIdentity(callerPackageName); } else { caller = getCallerIdentity(); @@ -6407,7 +6451,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ActiveAdmin admin; // Make sure the caller has any active admin with the right policy or // the required permission. - if (isUnicornFlagEnabled()) { + if (Flags.lockNowCoexistence()) { admin = enforcePermissionsAndGetEnforcingAdmin( /* admin= */ null, /* permissions= */ new String[]{MANAGE_DEVICE_POLICY_LOCK, LOCK_DEVICE}, @@ -9179,13 +9223,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeEnabledCoexistence()) { caller = getCallerIdentity(who, callerPackageName); } else { caller = getCallerIdentity(who); } - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeEnabledCoexistence()) { // The effect of this policy is device-wide. enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL); } else { @@ -9213,13 +9257,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeEnabledCoexistence()) { caller = getCallerIdentity(who, callerPackageName); } else { caller = getCallerIdentity(who); } - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeEnabledCoexistence()) { enforceCanQuery(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL); } else { Objects.requireNonNull(who, "ComponentName is null"); @@ -9242,13 +9286,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeZoneEnabledCoexistence()) { caller = getCallerIdentity(who, callerPackageName); } else { caller = getCallerIdentity(who); } - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeZoneEnabledCoexistence()) { // The effect of this policy is device-wide. EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( who, @@ -9288,13 +9332,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeZoneEnabledCoexistence()) { caller = getCallerIdentity(who, callerPackageName); } else { caller = getCallerIdentity(who); } - if (isUnicornFlagEnabled()) { + if (Flags.setAutoTimeZoneEnabledCoexistence()) { // The effect of this policy is device-wide. enforceCanQuery(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL); } else { @@ -9544,7 +9588,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.setKeyguardDisabledFeaturesCoexistence()) { caller = getCallerIdentity(who, callerPackageName); } else { caller = getCallerIdentity(who); @@ -9554,7 +9598,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userHandle = caller.getUserId(); int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; synchronized (getLockObject()) { - if (isUnicornFlagEnabled()) { + if (Flags.setKeyguardDisabledFeaturesCoexistence()) { // SUPPORT USES_POLICY_DISABLE_KEYGUARD_FEATURES EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( who, MANAGE_DEVICE_POLICY_KEYGUARD, caller.getPackageName(), @@ -9633,7 +9677,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { if (who != null) { - if (isUnicornFlagEnabled()) { + if (Flags.setKeyguardDisabledFeaturesCoexistence()) { EnforcingAdmin admin = getEnforcingAdminForPackage( who, who.getPackageName(), userHandle); Integer features = mDevicePolicyEngine.getLocalPolicySetByAdmin( @@ -9652,7 +9696,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // the different behaviour between a profile with separate challenge vs a profile with // unified challenge, which was part of getActiveAdminsForLockscreenPoliciesLocked() // before the migration. - if (isUnicornFlagEnabled()) { + if (Flags.setKeyguardDisabledFeaturesCoexistence()) { Integer features = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.KEYGUARD_DISABLED_FEATURES, affectedUserId); @@ -11320,7 +11364,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mOwners.hasDeviceOwner()) { return false; } - + final ComponentName profileOwner = getProfileOwnerAsUser(userId); if (profileOwner == null) { return false; @@ -11329,7 +11373,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (isManagedProfile(userId)) { return false; } - + return true; } private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) { @@ -11845,7 +11889,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("Invalid package name: " + validationResult); } - if (isUnicornFlagEnabled()) { + if (Flags.setApplicationRestrictionsCoexistence()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( who, MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, @@ -13228,7 +13272,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { String packageName, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); - if (isUnicornFlagEnabled()) { + // IMPORTANT: The code behind the if branch is OUTDATED and requires additional work before + // enabling the feature flag below. + // TODO(b/369141952): Update DPM.getApplicationRestrictions coexistence code + if (Flags.setApplicationRestrictionsCoexistence()) { EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( who, MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, @@ -13328,21 +13375,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); ActiveAdmin admin; - if (isUnicornFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && isCallerDelegate(caller, - DELEGATION_PACKAGE_ACCESS))); - synchronized (getLockObject()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && isCallerDelegate(caller, + DELEGATION_PACKAGE_ACCESS))); + synchronized (getLockObject()) { + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); } checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED); @@ -15585,12 +15623,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public boolean setStatusBarDisabled(ComponentName who, String callerPackageName, boolean disabled) { CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (isSetStatusBarDisabledCoexistenceEnabled()) { caller = getCallerIdentity(who, callerPackageName); } else { caller = getCallerIdentity(who); } - if (isUnicornFlagEnabled()) { + if (isSetStatusBarDisabledCoexistenceEnabled()) { enforcePermission(MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), UserHandle.USER_ALL); } else { @@ -15601,7 +15639,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int userId = caller.getUserId(); synchronized (getLockObject()) { - if (!isUnicornFlagEnabled()) { + if (!isSetStatusBarDisabledCoexistenceEnabled()) { Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId), "Admin " + who + " is neither the device owner or affiliated " + "user's profile owner."); @@ -15660,7 +15698,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isStatusBarDisabled(String callerPackage) { final CallerIdentity caller = getCallerIdentity(callerPackage); - if (isUnicornFlagEnabled()) { + if (isSetStatusBarDisabledCoexistenceEnabled()) { enforceCanQuery( MANAGE_DEVICE_POLICY_STATUS_BAR, caller.getPackageName(), caller.getUserId()); } else { @@ -15670,7 +15708,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int userId = caller.getUserId(); synchronized (getLockObject()) { - if (!isUnicornFlagEnabled()) { + if (!isSetStatusBarDisabledCoexistenceEnabled()) { Preconditions.checkCallAuthorization(isUserAffiliatedWithDeviceLocked(userId), "Admin " + callerPackage + " is neither the device owner or affiliated user's profile owner."); @@ -16862,7 +16900,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } EnforcingAdmin enforcingAdmin; - if (isUnicornFlagEnabled()) { + if (Flags.setPermissionGrantStateCoexistence()) { enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, @@ -17047,7 +17085,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public int getPermissionGrantState(ComponentName admin, String callerPackage, String packageName, String permission) throws RemoteException { final CallerIdentity caller = getCallerIdentity(admin, callerPackage); - if (isUnicornFlagEnabled()) { + if (Flags.setPermissionGrantStateCoexistence()) { enforceCanQuery(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, caller.getPackageName(), caller.getUserId()); } else { @@ -18177,6 +18215,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } + @GuardedBy("getLockObject()") private boolean areAllUsersAffiliatedWithDeviceLocked() { return mInjector.binderWithCleanCallingIdentity(() -> { final List userInfos = mUserManager.getAliveUsers(); @@ -18274,10 +18313,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(admin, packageName); if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), - UserHandle.USER_ALL); + synchronized (getLockObject()) { + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); + enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), + UserHandle.USER_ALL); + } } else { if (admin != null) { Preconditions.checkCallAuthorization( @@ -18289,8 +18330,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); } - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); + synchronized (getLockObject()) { + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); + } } DevicePolicyEventLogger @@ -19331,14 +19374,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("token must be at least 32-byte long"); } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { caller = getCallerIdentity(admin, callerPackageName); } else { caller = getCallerIdentity(admin); } final int userId = caller.getUserId(); - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, @@ -19351,6 +19394,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long tokenHandle = addEscrowToken( token, currentTokenHandle == null ? 0 : currentTokenHandle, userId); if (tokenHandle == 0) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.RESET_PASSWORD_TOKEN, + enforcingAdmin, + userId); return false; } mDevicePolicyEngine.setLocalPolicy( @@ -19394,7 +19441,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { caller = getCallerIdentity(admin, callerPackageName); } else { caller = getCallerIdentity(admin); @@ -19402,7 +19449,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userId = caller.getUserId(); boolean result = false; - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, @@ -19441,14 +19488,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { caller = getCallerIdentity(admin, callerPackageName); } else { caller = getCallerIdentity(admin); } int userId = caller.getUserId(); - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, @@ -19490,7 +19537,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Objects.requireNonNull(token); CallerIdentity caller; - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { caller = getCallerIdentity(admin, callerPackageName); } else { caller = getCallerIdentity(admin); @@ -19500,7 +19547,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean result = false; final String password = passwordOrNull != null ? passwordOrNull : ""; - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, MANAGE_DEVICE_POLICY_RESET_PASSWORD, @@ -19531,7 +19578,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (result) { - if (isUnicornFlagEnabled()) { + if (Flags.resetPasswordWithTokenCoexistence()) { DevicePolicyEventLogger .createEvent(DevicePolicyEnums.RESET_PASSWORD_WITH_TOKEN) .setAdmin(callerPackageName) @@ -23812,7 +23859,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG); } - static boolean isUnicornFlagEnabled() { + private static boolean isSetStatusBarDisabledCoexistenceEnabled() { return false; } @@ -24255,8 +24302,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { synchronized (getLockObject()) { Slogf.i(LOG_TAG, "Started device policies migration to the device policy engine."); - if (isUnicornFlagEnabled()) { + // TODO(b/359188869): Move this to the current migration method. + if (Flags.setAutoTimeZoneEnabledCoexistence()) { migrateAutoTimezonePolicy(); + } + if (Flags.setPermissionGrantStateCoexistence()) { migratePermissionGrantStatePolicies(); } migratePermittedInputMethodsPolicyLocked(); @@ -24283,12 +24333,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { maybeMigrateSecurityLoggingPolicyLocked(); // ID format: ..' String unmanagedBackupId = "35.1.unmanaged-mode"; - boolean migrated = false; - migrated = migrated | maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId); - migrated = migrated | maybeMigrateSuspendedPackagesLocked(unmanagedBackupId); - if (migrated) { + boolean unmanagedMigrated = false; + unmanagedMigrated = + unmanagedMigrated | maybeMigrateRequiredPasswordComplexityLocked(unmanagedBackupId); + unmanagedMigrated = + unmanagedMigrated | maybeMigrateSuspendedPackagesLocked(unmanagedBackupId); + if (unmanagedMigrated) { Slogf.i(LOG_TAG, "Backup made: " + unmanagedBackupId); } + + String supervisionBackupId = "36.2.supervision-support"; + boolean supervisionMigrated = maybeMigrateResetPasswordTokenLocked(supervisionBackupId); + if (supervisionMigrated) { + Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId); + } + // Additional migration steps should repeat the pattern above with a new backupId. } @@ -24488,7 +24547,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } }); } - + + @GuardedBy("getLockObject()") private void migrateUserControlDisabledPackagesLocked() { Binder.withCleanCallingIdentity(() -> { List users = mUserManager.getUsers(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 3f9605ac2e5d48ed84e576b0123f75c9386bc359..b3c8408ff54ba45e83f7bcad3e01843f0520a787 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -669,6 +669,19 @@ class Owners { } } + void markResetPasswordWithTokenMigrated() { + synchronized (mData) { + mData.mResetPasswordWithTokenMigrated = true; + mData.writeDeviceOwner(); + } + } + + boolean isResetPasswordWithTokenMigrated() { + synchronized (mData) { + return mData.mResetPasswordWithTokenMigrated; + } + } + @GuardedBy("mData") void pushToAppOpsLocked() { if (!mSystemReady) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java index 87fd0024a0fadc5fd188c496ebd0c67c22d53aae..10e43d955fabf56a707610af559c60e94fdc48d3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -91,6 +91,8 @@ class OwnersData { private static final String ATTR_REQUIRED_PASSWORD_COMPLEXITY_MIGRATED = "passwordComplexityMigrated"; private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated"; + private static final String ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED = + "resetPasswordWithTokenMigrated"; private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade"; // Internal state for the device owner package. @@ -122,6 +124,7 @@ class OwnersData { boolean mSecurityLoggingMigrated = false; boolean mRequiredPasswordComplexityMigrated = false; boolean mSuspendedPackagesMigrated = false; + boolean mResetPasswordWithTokenMigrated = false; boolean mPoliciesMigratedPostUpdate = false; @@ -417,7 +420,10 @@ class OwnersData { mSuspendedPackagesMigrated); } - + if (Flags.resetPasswordWithTokenCoexistence()) { + out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, + mResetPasswordWithTokenMigrated); + } out.endTag(null, TAG_POLICY_ENGINE_MIGRATION); } @@ -488,6 +494,9 @@ class OwnersData { mSuspendedPackagesMigrated = Flags.unmanagedModeMigration() && parser.getAttributeBoolean(null, ATTR_SUSPENDED_PACKAGES_MIGRATED, false); + mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence() + && parser.getAttributeBoolean(null, + ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false); break; default: diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 8068d46d6a9d32d2acf0cb4cb18afdab8326d411..4d9abf1d6be0a87fe9af158b8e59a9dd8a063f87 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -78,7 +78,7 @@ final class PolicyEnforcerCallbacks { } static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) { - if (!DevicePolicyManagerService.isUnicornFlagEnabled()) { + if (!Flags.setAutoTimeZoneEnabledCoexistence()) { Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off."); return true; } @@ -95,7 +95,7 @@ final class PolicyEnforcerCallbacks { static boolean setPermissionGrantState( @Nullable Integer grantState, @NonNull Context context, int userId, @NonNull PolicyKey policyKey) { - if (!DevicePolicyManagerService.isUnicornFlagEnabled()) { + if (!Flags.setPermissionGrantStateCoexistence()) { Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off."); return true; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3b334ec69231a2f9aca045409c6653079f86622d..ce6f1ecc946324d75d6e6aacce3fbe2f94623972 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -103,6 +103,7 @@ import com.android.i18n.timezone.ZoneInfoDb; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.ApplicationSharedMemory; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; @@ -204,6 +205,7 @@ import com.android.server.os.SchedulingPolicyService; import com.android.server.pdb.PersistentDataBlockService; import com.android.server.people.PeopleService; import com.android.server.permission.access.AccessCheckingService; +import com.android.server.pinner.PinnerService; import com.android.server.pm.ApexManager; import com.android.server.pm.ApexSystemServiceInfo; import com.android.server.pm.BackgroundInstallControlService; @@ -940,6 +942,12 @@ public final class SystemServer implements Dumpable { // Setup the default WTF handler RuntimeInit.setDefaultApplicationWtfHandler(SystemServer::handleEarlySystemWtf); + // Initialize the application shared memory region. + // This needs to happen before any system services are started, + // as they may rely on the shared memory region having been initialized. + ApplicationSharedMemory instance = ApplicationSharedMemory.create(); + ApplicationSharedMemory.setInstance(instance); + // Start services. try { t.traceBegin("StartServices"); @@ -3009,9 +3017,13 @@ public final class SystemServer implements Dumpable { } t.traceEnd(); - t.traceBegin("GameManagerService"); - mSystemServiceManager.startService(GameManagerService.Lifecycle.class); - t.traceEnd(); + if (!isWatch || !android.server.Flags.removeGameManagerServiceFromWear()) { + t.traceBegin("GameManagerService"); + mSystemServiceManager.startService(GameManagerService.Lifecycle.class); + t.traceEnd(); + } else { + Slog.d(TAG, "Not starting GameManagerService"); + } if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)) { t.traceBegin("UwbService"); diff --git a/services/java/com/android/server/flags.aconfig b/services/java/com/android/server/flags.aconfig index ec74ef191b81487d240ed5c448fa469149399f35..09906912ef3f7ea8de776eb7d5e374e0a3a6c72a 100644 --- a/services/java/com/android/server/flags.aconfig +++ b/services/java/com/android/server/flags.aconfig @@ -35,4 +35,12 @@ flag { namespace: "wear_systems" description: "Allow NetworkTimeUpdateService on Wear" bug: "327508176" +} + +flag { + name: "remove_game_manager_service_from_wear" + namespace: "wear_frameworks" + description: "Remove GameManagerService from Wear" + bug: "340929737" + is_fixed_read_only: true } \ No newline at end of file diff --git a/services/supervision/Android.bp b/services/supervision/Android.bp index 93a0c4af78914ae7060300f0d10bb0f1d1851616..aefbbcadcc1d742e4b2161119944f0dc4eab0bb7 100644 --- a/services/supervision/Android.bp +++ b/services/supervision/Android.bp @@ -19,4 +19,7 @@ java_library_static { defaults: ["platform_service_defaults"], srcs: [":services.supervision-sources"], libs: ["services.core"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } diff --git a/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java new file mode 100644 index 0000000000000000000000000000000000000000..fead05bc7e492c5da60b0e5d885c722363bc42ff --- /dev/null +++ b/services/supervision/java/com/android/server/supervision/SupervisionManagerInternal.java @@ -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. + */ + +package com.android.server.supervision; + +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.os.Bundle; + +/** + * Local system service interface for {@link SupervisionService}. + * + * @hide Only for use within Android OS. + */ +public abstract class SupervisionManagerInternal { + /** + * Returns whether supervision is enabled for the specified user + * + * @param userId The user to retrieve the supervision state for + * @return whether the user is supervised + */ + public abstract boolean isSupervisionEnabledForUser(@UserIdInt int userId); + + /** + * Sets whether the supervision lock screen should be shown for the specified user + * + * @param userId The user set the superivision state for + * @param enabled Whether or not the superivision lock screen needs to be shown + * @param options Optional configuration parameters for the supervision lock screen + */ + public abstract void setSupervisionLockscreenEnabledForUser( + @UserIdInt int userId, boolean enabled, @Nullable Bundle options); +} diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java index 7ffd0eca9b960dca44bd7fed28d03a151aa8eebd..4c515c173c8d82f173ad98d85ebde239f219ce9c 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionService.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java @@ -18,14 +18,22 @@ package com.android.server.supervision; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.supervision.ISupervisionManager; import android.content.Context; +import android.content.pm.UserInfo; +import android.os.Bundle; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; +import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.pm.UserManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -38,13 +46,25 @@ public class SupervisionService extends ISupervisionManager.Stub { private final Context mContext; + // TODO(b/362756788): Does this need to be a LockGuard lock? + private final Object mLockDoNoUseDirectly = new Object(); + + @GuardedBy("getLockObject()") + private final SparseArray mUserData = new SparseArray<>(); + + private final UserManagerInternal mUserManagerInternal; + public SupervisionService(Context context) { - mContext = context.createAttributionContext("SupervisionService"); + mContext = context.createAttributionContext(LOG_TAG); + mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener()); } @Override - public boolean isSupervisionEnabled() { - return false; + public boolean isSupervisionEnabledForUser(@UserIdInt int userId) { + synchronized (getLockObject()) { + return getUserDataLocked(userId).supervisionEnabled; + } } @Override @@ -60,11 +80,44 @@ public class SupervisionService extends ISupervisionManager.Stub { } @Override - protected void dump( - @NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, fout)) return; + protected void dump(@NonNull FileDescriptor fd, + @NonNull PrintWriter printWriter, @Nullable String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, printWriter)) return; + + try (var pw = new IndentingPrintWriter(printWriter, " ")) { + pw.println("SupervisionService state:"); + pw.increaseIndent(); + + var users = mUserManagerInternal.getUsers(false); + synchronized (getLockObject()) { + for (var user : users) { + getUserDataLocked(user.id).dump(pw); + pw.println(); + } + } + } + } + + private Object getLockObject() { + return mLockDoNoUseDirectly; + } - fout.println("Supervision enabled: " + isSupervisionEnabled()); + @NonNull + @GuardedBy("getLockObject()") + SupervisionUserData getUserDataLocked(@UserIdInt int userId) { + SupervisionUserData data = mUserData.get(userId); + if (data == null) { + // TODO(b/362790738): Do not create user data for nonexistent users. + data = new SupervisionUserData(userId); + mUserData.append(userId, data); + } + return data; + } + + void setSupervisionEnabledForUser(@UserIdInt int userId, boolean enabled) { + synchronized (getLockObject()) { + getUserDataLocked(userId).supervisionEnabled = enabled; + } } public static class Lifecycle extends SystemService { @@ -77,7 +130,35 @@ public class SupervisionService extends ISupervisionManager.Stub { @Override public void onStart() { + publishLocalService(SupervisionManagerInternal.class, mSupervisionService.mInternal); publishBinderService(Context.SUPERVISION_SERVICE, mSupervisionService); } } + + final SupervisionManagerInternal mInternal = new SupervisionManagerInternal() { + public boolean isSupervisionEnabledForUser(@UserIdInt int userId) { + synchronized (getLockObject()) { + return getUserDataLocked(userId).supervisionEnabled; + } + } + + @Override + public void setSupervisionLockscreenEnabledForUser( + @UserIdInt int userId, boolean enabled, @Nullable Bundle options) { + synchronized (getLockObject()) { + SupervisionUserData data = getUserDataLocked(userId); + data.supervisionLockScreenEnabled = enabled; + data.supervisionLockScreenOptions = options; + } + } + }; + + private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener { + @Override + public void onUserRemoved(UserInfo user) { + synchronized (getLockObject()) { + mUserData.remove(user.id); + } + } + } } diff --git a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java index 3aba24a3d4a511fd3de0d98d1cef84091de249cf..2adaae3943f1f0972ba1360c4243156213f83175 100644 --- a/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java +++ b/services/supervision/java/com/android/server/supervision/SupervisionServiceShellCommand.java @@ -17,8 +17,7 @@ package com.android.server.supervision; import android.os.ShellCommand; - -import java.io.PrintWriter; +import android.os.UserHandle; public class SupervisionServiceShellCommand extends ShellCommand { private final SupervisionService mService; @@ -32,30 +31,29 @@ public class SupervisionServiceShellCommand extends ShellCommand { if (cmd == null) { return handleDefaultCommands(null); } - final PrintWriter pw = getOutPrintWriter(); switch (cmd) { - case "help": return help(pw); - case "is-enabled": return isEnabled(pw); + case "enable": return setEnabled(true); + case "disable": return setEnabled(false); default: return handleDefaultCommands(cmd); } } - private int help(PrintWriter pw) { - pw.println("Supervision service commands:"); - pw.println(" help"); - pw.println(" Prints this help text"); - pw.println(" is-enabled"); - pw.println(" Is supervision enabled"); - return 0; - } - - private int isEnabled(PrintWriter pw) { - pw.println(mService.isSupervisionEnabled()); + private int setEnabled(boolean enabled) { + final var pw = getOutPrintWriter(); + final var userId = UserHandle.parseUserArg(getNextArgRequired()); + mService.setSupervisionEnabledForUser(userId, enabled); return 0; } @Override public void onHelp() { - help(getOutPrintWriter()); + final var pw = getOutPrintWriter(); + pw.println("Supervision service (supervision) commands:"); + pw.println(" help"); + pw.println(" Prints this help text"); + pw.println(" enable "); + pw.println(" Enables supervision for the given user."); + pw.println(" disable "); + pw.println(" Disables supervision for the given user."); } } diff --git a/services/supervision/java/com/android/server/supervision/SupervisionUserData.java b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java new file mode 100644 index 0000000000000000000000000000000000000000..56162372f74017ad59ea5069bf82e21511dfefa1 --- /dev/null +++ b/services/supervision/java/com/android/server/supervision/SupervisionUserData.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.supervision; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.os.Bundle; +import android.util.IndentingPrintWriter; + +/** User specific data, used internally by the {@link SupervisionService}. */ +public class SupervisionUserData { + public final @UserIdInt int userId; + public boolean supervisionEnabled; + public boolean supervisionLockScreenEnabled; + @Nullable public Bundle supervisionLockScreenOptions; + + public SupervisionUserData(@UserIdInt int userId) { + this.userId = userId; + } + + void dump(@NonNull IndentingPrintWriter pw) { + pw.println(); + pw.println("User " + userId + ":"); + pw.increaseIndent(); + pw.println("supervisionEnabled: " + supervisionEnabled); + pw.println("supervisionLockScreenEnabled: " + supervisionLockScreenEnabled); + pw.println("supervisionLockScreenOptions: " + supervisionLockScreenOptions); + pw.decreaseIndent(); + } +} diff --git a/services/supervision/lint-baseline.xml b/services/supervision/lint-baseline.xml new file mode 100644 index 0000000000000000000000000000000000000000..f2a501037447c066c4e4fbf2cbe1ae805b3afbfe --- /dev/null +++ b/services/supervision/lint-baseline.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java index e5d315358df66709f621a1a3fd4a9caf9a5f666f..72cbac331551127a49e41f0ab2c71432df77a67d 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTests.java @@ -35,6 +35,7 @@ import java.io.StringWriter; public final class InputMethodManagerServiceTests { static final int SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 2; static final int NO_SYSTEM_DECORATION_SUPPORT_DISPLAY_ID = 3; + private static final int TEST_IME_USER_ID = 1; static InputMethodManagerService.ImeDisplayValidator sChecker = (displayId) -> { @@ -102,7 +103,8 @@ public final class InputMethodManagerServiceTests { null, null, null, - null)); + null, + TEST_IME_USER_ID)); history.dump(new PrintWriter(writer), "" /* prefix */); diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt index 5c4716dc751e11b794284012b2a842669aa78d94..7d5532f6e4010037ba37b4ece2358ed8482f7c23 100644 --- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt +++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt @@ -57,6 +57,7 @@ import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.doReturn @@ -383,6 +384,10 @@ class PackageManagerComponentLabelIconOverrideTest { android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)) { PackageManager.PERMISSION_GRANTED } + whenever(this.checkPermission( + eq(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyInt(), anyInt())) { + PackageManager.PERMISSION_GRANTED + } } val mockSharedLibrariesImpl: SharedLibrariesImpl = mock { whenever(this.snapshot()) { this@mock } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt index b21c34905badd8b0ba4cd6b9e8c2c609fc377df6..2144785ed8fdfe796154eeacdbd43cb2f6b1380c 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt @@ -94,6 +94,7 @@ class PackageStateTest { ParsedService::getIntents, ParsedService::getProperties, Intent::getCategories, + Intent::getExtraIntentKeys, PackageUserState::getDisabledComponents, PackageUserState::getEnabledComponents, PackageUserState::getSharedLibraryOverlayPaths, diff --git a/services/tests/RemoteProvisioningServiceTests/Android.bp b/services/tests/RemoteProvisioningServiceTests/Android.bp index 19c913620760a110abac4529e41930b9f7ab0c98..3a73c3954d522d627ca7e613d23971638270fec6 100644 --- a/services/tests/RemoteProvisioningServiceTests/Android.bp +++ b/services/tests/RemoteProvisioningServiceTests/Android.bp @@ -31,7 +31,6 @@ android_test { "service-rkp.impl", "services.core", "truth", - "truth-java8-extension", ], test_suites: [ "device-tests", diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java index 007c0db1b7316c67f0d3ebdaf744ca5fcbad152c..a1616c676dbdc415f79f02aa1cadddb727531afa 100644 --- a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java +++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java @@ -17,7 +17,6 @@ package com.android.server.security.rkp; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; diff --git a/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..413eb314c41dfba3829b0de35f6b935946ead676 --- /dev/null +++ b/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt @@ -0,0 +1,78 @@ +/* + * 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 android.app.appsearch.GenericDocument +import android.os.Parcel +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 GenericDocumentWrapperTest { + + @Test + fun parcelUnparcel() { + val doc = + GenericDocument.Builder>("", "", "") + .setPropertyLong("test", 42) + .build() + val wrapper = GenericDocumentWrapper(doc) + + val recovered = parcelUnparcel(wrapper) + + assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42) + } + + @Test + fun parcelUnparcel_afterGetValue() { + val doc = + GenericDocument.Builder>("", "", "") + .setPropertyLong("test", 42) + .build() + val wrapper = GenericDocumentWrapper(doc) + assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42) + + val recovered = parcelUnparcel(wrapper) + + assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42) + } + + + @Test + fun getValue() { + val doc = + GenericDocument.Builder>("", "", "") + .setPropertyLong("test", 42) + .build() + val wrapper = GenericDocumentWrapper(doc) + + assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42) + } + + private fun parcelUnparcel(obj: GenericDocumentWrapper): GenericDocumentWrapper { + val parcel = Parcel.obtain() + try { + obj.writeToParcel(parcel, 0) + parcel.setDataPosition(0) + return GenericDocumentWrapper.CREATOR.createFromParcel(parcel) + } finally { + parcel.recycle() + } + } +} \ No newline at end of file diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt index c05c3819ca2882b3318cd5b7cf56348cd9db69c8..bc64e158e8300a95c634fc6a3504888e9ae5cf37 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt @@ -36,7 +36,6 @@ import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.infra.AndroidFuture import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults import com.google.common.truth.Truth.assertThat -import com.google.common.util.concurrent.MoreExecutors import java.util.concurrent.atomic.AtomicBoolean import org.junit.Test import org.junit.runner.RunWith @@ -46,7 +45,6 @@ import org.junit.runners.JUnit4 class MetadataSyncAdapterTest { private val context = InstrumentationRegistry.getInstrumentation().targetContext private val appSearchManager = context.getSystemService(AppSearchManager::class.java) - private val testExecutor = MoreExecutors.directExecutor() private val packageManager = context.packageManager @Test @@ -138,8 +136,7 @@ class MetadataSyncAdapterTest { PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() runtimeSearchSession.put(putDocumentsRequest).get() staticSearchSession.put(putDocumentsRequest).get() - val metadataSyncAdapter = - MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager) val submitSyncRequest = metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( @@ -180,8 +177,7 @@ class MetadataSyncAdapterTest { val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() staticSearchSession.put(putDocumentsRequest).get() - val metadataSyncAdapter = - MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager) val submitSyncRequest = metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( @@ -236,8 +232,7 @@ class MetadataSyncAdapterTest { val putDocumentsRequest: PutDocumentsRequest = PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build() runtimeSearchSession.put(putDocumentsRequest).get() - val metadataSyncAdapter = - MetadataSyncAdapter(testExecutor, packageManager, appSearchManager) + val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager) val submitSyncRequest = metadataSyncAdapter.trySyncAppFunctionMetadataBlocking( diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index fd05b26c320b49bb1b0a29d9be877b62ca100080..8e1be9a777fd405b37cee311f58b47bf3e37400c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -392,7 +392,7 @@ public final class DisplayDeviceConfigTest { public void testInvalidLuxThrottling() throws Exception { setupDisplayDeviceConfigFromDisplayConfigFile( getContent(getInvalidLuxThrottling(), getValidProxSensor(), - /* includeIdleMode= */ true, /* enableEvenDimmer */ false)); + /* includeIdleMode= */ true, /* enableEvenDimmer= */ false)); Map> luxThrottlingData = mDisplayDeviceConfig.getLuxThrottlingData(); @@ -600,7 +600,7 @@ public final class DisplayDeviceConfigTest { public void testProximitySensorWithEmptyValuesFromDisplayConfig() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile( getContent(getValidLuxThrottling(), getProxSensorWithEmptyValues(), - /* includeIdleMode= */ true, /* enableEvenDimmer */ false)); + /* includeIdleMode= */ true, /* enableEvenDimmer= */ false)); assertNull(mDisplayDeviceConfig.getProximitySensor()); } @@ -608,7 +608,7 @@ public final class DisplayDeviceConfigTest { public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile( getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(), - /* includeIdleMode= */ true, /* enableEvenDimmer */ false)); + /* includeIdleMode= */ true, /* enableEvenDimmer= */ false)); assertEquals("test_proximity_sensor", mDisplayDeviceConfig.getProximitySensor().type); assertEquals("Test Proximity Sensor", @@ -803,7 +803,7 @@ public final class DisplayDeviceConfigTest { @Test public void testBrightnessRamps_IdleFallsBackToConfigInteractive() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), - getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false)); + getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false)); assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000); assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000); @@ -820,14 +820,14 @@ public final class DisplayDeviceConfigTest { @Test public void testBrightnessCapForWearBedtimeMode() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), - getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false)); + getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false)); assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA); } @Test public void testAutoBrightnessBrighteningLevels() throws IOException { setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), - getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false)); + getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false)); assertArrayEquals(new float[]{0.0f, 80}, mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux( @@ -890,7 +890,7 @@ public final class DisplayDeviceConfigTest { when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false); setupDisplayDeviceConfigFromConfigResourceFile(); setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), - getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false)); + getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false)); assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100), brightnessIntToFloat(150)}, @@ -929,7 +929,7 @@ public final class DisplayDeviceConfigTest { when(mFlags.isEvenDimmerEnabled()).thenReturn(true); when(mResources.getBoolean(R.bool.config_evenDimmerEnabled)).thenReturn(true); setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(), - getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true)); + getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ true)); assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable()); assertEquals(0.01f, mDisplayDeviceConfig.getBacklightFromBrightness(0.002f), ZERO_DELTA); @@ -1365,7 +1365,7 @@ public final class DisplayDeviceConfigTest { private String getContent() { return getContent(getValidLuxThrottling(), getValidProxSensor(), - /* includeIdleMode= */ true, false); + /* includeIdleMode= */ true, /* enableEvenDimmer= */ false); } private String getContent(String brightnessCapConfig, String proxSensor, diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index bf5a692ef8caad0d4870926d972c1476accf8745..c70bf8abaef69dd4033b7fe2e4b54cdfe449a6e0 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -2126,6 +2126,48 @@ public final class DisplayPowerControllerTest { /* ignoreAnimationLimits= */ anyBoolean()); } + @Test + public void testManualBrightness_stateDozePolicyOnUseNormalBrightnessForDozeTrue_brightnessDoze() { + when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); + when(mDisplayManagerFlagsMock.isNormalBrightnessForDozeParameterEnabled()).thenReturn(true); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + float brightness = 0.277f; + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness); + when(mHolder.hbmController.getCurrentBrightnessMax()) + .thenReturn(PowerManager.BRIGHTNESS_MAX); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + // Start with state=DOZE. + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE); + DisplayPowerRequest dprInit = new DisplayPowerRequest(); + dprInit.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dprInit, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState; initialize to DOZE + // Go to state=ON. But state change would be blocked. so, state=DOZE. + when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.dozeScreenState = Display.STATE_ON; + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + dpr.useNormalBrightnessForDoze = true; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState; process turning on. + + ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue(); + listener.onBrightnessChanged(brightness); + advanceTime(1); // Send messages, run updatePowerState + + // When state=DOZE, force doze brightness regardless the requested policy. + verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR), + /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(), + /* ignoreAnimationLimits= */ anyBoolean()); + } + @Test public void testDozeManualBrightness_AbcIsNull() { when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java index 120cc84193cda46b0e79f1491f3cb6a603a39a68..f5bed999d5a0f724babd13633bcf35540b02bca1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -29,8 +29,10 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -45,6 +47,7 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.util.Spline; import android.view.Display; import android.view.DisplayAddress; import android.view.SurfaceControl; @@ -59,6 +62,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.internal.R; import com.android.server.LocalServices; import com.android.server.display.LocalDisplayAdapter.BacklightAdapter; +import com.android.server.display.color.ColorDisplayService; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.display.notifications.DisplayNotificationManager; @@ -119,6 +123,8 @@ public class LocalDisplayAdapterTest { private DisplayManagerFlags mFlags; @Mock private DisplayPowerController mMockedDisplayPowerController; + @Mock + private ColorDisplayService.ColorDisplayServiceInternal mMockedColorDisplayServiceInternal; private Handler mHandler; @@ -132,6 +138,11 @@ public class LocalDisplayAdapterTest { private Injector mInjector; + @Mock + private DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock + private BacklightAdapter mMockBacklightAdapter; + @Mock private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f }; @@ -150,6 +161,9 @@ public class LocalDisplayAdapterTest { doReturn(mMockedResources).when(mMockedContext).getResources(); LocalServices.removeServiceForTest(LightsManager.class); LocalServices.addService(LightsManager.class, mMockedLightsManager); + LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); + LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class, + mMockedColorDisplayServiceInternal); mInjector = new Injector(); when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true); mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler, @@ -211,7 +225,15 @@ public class LocalDisplayAdapterTest { when(mMockedResources.getIntArray( com.android.internal.R.array.config_autoBrightnessLcdBacklightValues)) .thenReturn(new int[]{}); + + when(mMockedColorDisplayServiceInternal.fetchEvenDimmerSpline(3)).thenReturn( + new Spline.LinearSpline( + new float[]{2f, 3.0f, 500f, 2000f}, + new float[]{100, 0, 0, 0})); + when(mMockDisplayDeviceConfig.isEvenDimmerAvailable()).thenReturn(true); + doReturn(true).when(mFlags).isDisplayOffloadEnabled(); + doReturn(true).when(mFlags).isEvenDimmerEnabled(); initDisplayOffloadSession(); } @@ -222,6 +244,122 @@ public class LocalDisplayAdapterTest { } } + @Test + public void testEvenDimmer() throws InterruptedException { + // Set up + FakeDisplay display = new FakeDisplay(PORT_A); + setUpDisplay(display); + updateAvailableDisplays(); + mAdapter.registerLocked(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + assertThat(mListener.addedDisplays.size()).isEqualTo(1); + DisplayDevice displayDevice = mListener.addedDisplays.get(0); + + // brightness|backlight| nits | strength + // 0.5 | 0.45 | 600 | 0 // initial setup value + // 0.4 | 0.35 | 500 | 0 // normal range value + // 0.31 | 0.2 | 3 | 0 // transition point + // 0.16 | 0.125 | 2.5 | 50 // mid point of even dimmer + // 0.1 | 0.05 | 2 | 100 // bottom of even dimmer range + // 0.05 | 0.01 | 1 | 100+ // beyond strength=100 range (should still return 100) + when(mMockDisplayDeviceConfig.getEvenDimmerTransitionPoint()).thenReturn(0.31f); + when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.5f)).thenReturn(0.45f); + when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.4f)).thenReturn(0.35f); + when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.31f)).thenReturn(0.2f); + when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.16f)).thenReturn(0.125f); + when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.1f)).thenReturn(0.05f); + when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.05f)).thenReturn(0.01f); + when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.45f)).thenReturn(600f); + when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.35f)).thenReturn(500f); + when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.2f)).thenReturn(3f); + when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.125f)).thenReturn(2.5f); + when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.05f)).thenReturn(2f); + when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.01f)).thenReturn(1f); + + // initialise brightness to 0.5 + Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, + 0.5f, 0.5f, null); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + verify(mSurfaceControlProxy).setDisplayPowerMode(any(), anyInt()); + verify(mMockBacklightAdapter).setBacklight(anyFloat(), anyFloat(), anyFloat(), anyFloat()); + verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(false), eq(0)); + verify(mMockedColorDisplayServiceInternal).fetchEvenDimmerSpline(eq(3.0f)); + + // set up normal brightness range + changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f, + null); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + // verify normal brightness range + verify(mMockBacklightAdapter).setBacklight(0.35f, 500f, 0.35f, 500f); + verify(mMockedColorDisplayServiceInternal, + times(1)) // no more, since the strength is the same + .applyEvenDimmerColorChanges(eq(false), eq(0)); + verify(mMockedColorDisplayServiceInternal, times(2)).fetchEvenDimmerSpline(eq(3.0f)); + + // set up even dimmer edge range + changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.31f, + 0.31f, null); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + // verify even dimmer edge range + verify(mMockBacklightAdapter).setBacklight(0.2f, 3f, 0.2f, 3f); + // verify no more times, since the strength and enabled-ness is the same + verify(mMockedColorDisplayServiceInternal, times(1)).applyEvenDimmerColorChanges(eq(false), + eq(0)); + verify(mMockedColorDisplayServiceInternal, times(3)).fetchEvenDimmerSpline(eq(3.0f)); + + // set up mid point of even dimmer range + changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.16f, + 0.16f, null); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + // verify within even dimmer range + verify(mMockBacklightAdapter).setBacklight(0.125f, 2.5f, 0.125f, 2.5f); + verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(50)); + verify(mMockedColorDisplayServiceInternal, times(4)).fetchEvenDimmerSpline(eq(3.0f)); + + // set up within even dimmer range + changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.1f, 0.1f, + null); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + // verify within even dimmer range + verify(mMockBacklightAdapter).setBacklight(0.05f, 2f, 0.05f, 2f); + verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100)); + verify(mMockedColorDisplayServiceInternal, times(5)).fetchEvenDimmerSpline(eq(3.0f)); + + // set up below even dimmer range + changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.05f, + 0.05f, null); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + // verify within even dimmer range + verify(mMockBacklightAdapter).setBacklight(0.01f, 1f, 0.01f, 1f); + // ensure no greater than 100 strength is returned, therefore not called again. + verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100)); + verify(mMockedColorDisplayServiceInternal, times(6)).fetchEvenDimmerSpline(eq(3.0f)); + + // set up return to normal range + changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f, + null); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + changeStateRunnable.run(); + waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS); + // verify return to normal range + verify(mMockBacklightAdapter, times(2)).setBacklight(0.35f, 500f, 0.35f, 500f); + verify(mMockedColorDisplayServiceInternal, times(2)).applyEvenDimmerColorChanges(eq(false), + anyInt()); + verify(mMockedColorDisplayServiceInternal, times(7)).fetchEvenDimmerSpline(eq(3.0f)); + } + /** * Confirm that display is marked as private when it is listed in * com.android.internal.R.array.config_localPrivateDisplayPorts. @@ -1461,15 +1599,16 @@ public class LocalDisplayAdapterTest { return mSurfaceControlProxy; } - // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay) - // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure - // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting - // consistent behaviour. Please also note that context passed to this method, is - // mMockContext and values will be loaded from mMockResources. @Override public DisplayDeviceConfig createDisplayDeviceConfig(Context context, long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) { - return DisplayDeviceConfig.create(context, isFirstDisplay, flags); + return mMockDisplayDeviceConfig; + } + + @Override + public BacklightAdapter getBacklightAdapter(IBinder displayToken, boolean isFirstDisplay, + LocalDisplayAdapter.SurfaceControlProxy surfaceControlProxy) { + return mMockBacklightAdapter; } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index 50f814da64887171d26fb8a930bce5ccbb2e3b13..efa8b3ef775f8169dd48eecc0ff9a9695421ba29 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -397,9 +397,9 @@ public class AutomaticBrightnessStrategyTest { mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE, allowAutoBrightnessWhileDozing, brightnessReason, policy, useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged); - // 1st AUTO_BRIGHTNESS_MODE_DEFAULT - verify(mAutomaticBrightnessController).switchMode( - AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT, + // 3rd AUTO_BRIGHTNESS_MODE_DOZE + verify(mAutomaticBrightnessController, times(3)).switchMode( + AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE, /* sendUpdate= */ false); // Validate interaction when automaticBrightnessController is in non-idle mode, display @@ -407,8 +407,8 @@ public class AutomaticBrightnessStrategyTest { mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, allowAutoBrightnessWhileDozing, brightnessReason, policy, useNormalBrightnessForDoze, lastUserSetBrightness, userSetBrightnessChanged); - // 2nd AUTO_BRIGHTNESS_MODE_DEFAULT - verify(mAutomaticBrightnessController, times(2)).switchMode( + // AUTO_BRIGHTNESS_MODE_DEFAULT + verify(mAutomaticBrightnessController).switchMode( AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT, /* sendUpdate= */ false); } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java index 1cad255b85d77b12d77552c4141f75bec4fea831..e863f15749323a3e19ce1825d36f3b2ef0677b6f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java @@ -40,7 +40,9 @@ import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.text.TextUtils; import com.android.internal.os.Clock; @@ -87,6 +89,7 @@ public class ApplicationStartInfoTest { private static final String APP_1_PACKAGE_NAME = "com.android.test.stub1"; @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule(); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private AppOpsService mAppOpsService; @Mock private PackageManagerInternal mPackageManagerInt; @@ -144,6 +147,7 @@ public class ApplicationStartInfoTest { } @Test + @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_COMPONENT) public void testApplicationStartInfo() throws Exception { // Make sure we can write to the file. assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir)); @@ -167,7 +171,7 @@ public class ApplicationStartInfoTest { ArrayList list = new ArrayList(); // Case 1: Activity start intent failed - mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), + mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT), appStartTimestampIntentStarted); mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list); verifyInProgressRecordsSize(1); @@ -185,7 +189,7 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_TYPE_UNSET, // state type ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode - mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted); + mAppStartInfoTracker.onActivityIntentFailed(appStartTimestampIntentStarted); list.clear(); mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list); verifyInProgressRecordsSize(0); @@ -194,7 +198,7 @@ public class ApplicationStartInfoTest { mAppStartInfoTracker.clearProcessStartInfo(true); // Case 2: Activity start launch cancelled - mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), + mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT), appStartTimestampIntentStarted); list.clear(); mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list); @@ -236,12 +240,13 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason ApplicationStartInfo.STARTUP_STATE_ERROR, // startup state ApplicationStartInfo.START_TYPE_COLD, // state type - ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode + ApplicationStartInfo.START_COMPONENT_ACTIVITY); // start component mAppStartInfoTracker.clearProcessStartInfo(true); // Case 3: Activity start success - mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), + mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT), appStartTimestampIntentStarted); list.clear(); mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list); @@ -255,6 +260,7 @@ public class ApplicationStartInfoTest { verifyInProgressRecordsSize(1); assertEquals(list.size(), 1); + // The records will now be in both backing data structures, so verify in each. verifyInProgressApplicationStartInfo( 0, // index APP_1_PID_1, // pid @@ -277,7 +283,8 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state ApplicationStartInfo.START_TYPE_COLD, // state type - ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode + ApplicationStartInfo.START_COMPONENT_ACTIVITY); // start component mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT, appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD); @@ -300,7 +307,7 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_TYPE_COLD, // state type ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode - mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted, + mAppStartInfoTracker.onActivityReportFullyDrawn(appStartTimestampIntentStarted, appStartTimestampReportFullyDrawn); list.clear(); mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list); @@ -317,7 +324,8 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state ApplicationStartInfo.START_TYPE_COLD, // state type - ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode + ApplicationStartInfo.START_COMPONENT_ACTIVITY); // start component // Don't clear records for use in subsequent cases. @@ -347,7 +355,8 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_REASON_SERVICE, // reason ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state ApplicationStartInfo.START_TYPE_COLD, // state type - ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode + ApplicationStartInfo.START_COMPONENT_SERVICE); // start component // Case 5: Create an instance of app1 with a different user started for a broadcast sleep(1); @@ -376,7 +385,8 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_REASON_BROADCAST, // reason ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state ApplicationStartInfo.START_TYPE_COLD, // state type - ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode + ApplicationStartInfo.START_COMPONENT_BROADCAST); // start component // Case 6: User 2 gets removed mAppStartInfoTracker.onPackageRemoved(APP_1_PACKAGE_NAME, APP_1_UID_USER_2, false); @@ -422,7 +432,9 @@ public class ApplicationStartInfoTest { ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state ApplicationStartInfo.START_TYPE_COLD, // state type - ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode + ApplicationStartInfo.LAUNCH_MODE_STANDARD, // launch mode + ApplicationStartInfo.START_COMPONENT_CONTENT_PROVIDER // start component + ); // Case 8: Save and load again ArrayList original = new ArrayList(); @@ -453,6 +465,7 @@ public class ApplicationStartInfoTest { */ @SuppressWarnings("GuardedBy") @Test + @EnableFlags(android.app.Flags.FLAG_APP_START_INFO_COMPONENT) public void testInProgressRecordsLimit() throws Exception { ProcessRecord app = makeProcessRecord( APP_1_PID_1, // pid @@ -466,7 +479,7 @@ public class ApplicationStartInfoTest { // never exceeds the expected size of MAX_IN_PROGRESS_RECORDS. for (int i = 0; i < AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS * 2; i++) { Long startTime = Long.valueOf(i); - mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), startTime); + mAppStartInfoTracker.onActivityIntentStarted(buildIntent(COMPONENT), startTime); verifyInProgressRecordsSize( Math.min(i + 1, AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS)); @@ -618,6 +631,10 @@ public class ApplicationStartInfoTest { } } + /** + * Convenience helper to access the record from the in progress data structure. Only applies for + * activity starts. + */ private void verifyInProgressApplicationStartInfo(int index, Integer pid, Integer uid, Integer packageUid, Integer definingUid, String processName, @@ -625,14 +642,15 @@ public class ApplicationStartInfoTest { synchronized (mAppStartInfoTracker.mLock) { verifyApplicationStartInfo(mAppStartInfoTracker.mInProgressRecords.valueAt(index), pid, uid, packageUid, definingUid, processName, reason, startupState, - startType, launchMode); + startType, launchMode, ApplicationStartInfo.START_COMPONENT_ACTIVITY); } } private void verifyApplicationStartInfo(ApplicationStartInfo info, Integer pid, Integer uid, Integer packageUid, Integer definingUid, String processName, - Integer reason, Integer startupState, Integer startType, Integer launchMode) { + Integer reason, Integer startupState, Integer startType, Integer launchMode, + Integer startComponent) { assertNotNull(info); if (pid != null) { @@ -662,6 +680,9 @@ public class ApplicationStartInfoTest { if (launchMode != null) { assertEquals(launchMode.intValue(), info.getLaunchMode()); } + if (startComponent != null) { + assertEquals(startComponent.intValue(), info.getStartComponent()); + } } private class TestInjector extends Injector { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java index 93066d8d113a5624687959e2e46efc87bc798c15..67475335fe51ea01ca78bfc7eb696ce8709656f3 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java @@ -213,7 +213,7 @@ public class AsyncProcessStartTest { any(), any(), any(), any(), any(), any(), any(), - any(), any(), + any(), any(), any(), anyLong(), anyLong()); final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid)); @@ -277,7 +277,7 @@ public class AsyncProcessStartTest { null, null, null, null, null, null, - null, null, null, + null, null, null, null, 0, 0); // Sleep until timeout should have triggered diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java index 014b98c64c66c044e37fde26689db3013fdbb68f..43becc59c3cbdecee3e97ce9dd17efbe422abec7 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java @@ -216,7 +216,7 @@ public class ProcessObserverTest { any(), any(), any(), any(), any(), any(), any(), - any(), any(), + any(), any(), any(), anyLong(), anyLong()); final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid)); r.setPid(myPid()); @@ -265,7 +265,7 @@ public class ProcessObserverTest { null, null, null, null, null, null, - null, null, null, + null, null, null, null, 0, 0); return app; } diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java index 6e416857abbf80432b182de3c276198227b140fe..e0c7bfe91890bc471a3eab63cc6afee74e242d0d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; @@ -36,6 +37,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; @@ -47,6 +49,7 @@ import android.os.MessageQueue; import android.os.SystemProperties; import android.platform.test.flag.junit.SetFlagsRule; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -145,6 +148,22 @@ public class RollbackPackageHealthObserverTest { } ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean())); + try { + when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { + final PackageInfo res = new PackageInfo(); + res.packageName = inv.getArgument(0); + res.setApexPackageName(res.packageName); + return res; + }); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + + Context testContext = InstrumentationRegistry.getInstrumentation() + .getTargetContext(); + when(mMockContext.getUser()).thenReturn(testContext.getUser()); + when(mMockContext.getPackageName()).thenReturn(testContext.getPackageName()); + SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(false)); } diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index fc4d8d871fd538ad39b597415960ad42994d2666..07029268661ebf268c2b6901ef6e2c30c2cc48c5 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -16,6 +16,9 @@ package com.android.server.power; +import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP; +import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE; + import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; @@ -31,11 +34,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.content.Context; import android.content.res.Resources; import android.hardware.SensorManager; import android.hardware.display.AmbientDisplayConfiguration; +import android.hardware.display.DisplayManagerInternal; import android.os.BatteryStats; import android.os.Handler; import android.os.IWakeLockCallback; @@ -48,11 +53,18 @@ import android.os.WorkSource; import android.os.test.TestLooper; import android.provider.Settings; import android.testing.TestableContext; +import android.util.IntArray; +import android.util.SparseBooleanArray; +import android.view.Display; +import android.view.DisplayAddress; +import android.view.DisplayInfo; import androidx.test.InstrumentationRegistry; import com.android.internal.app.IBatteryStats; import com.android.server.LocalServices; +import com.android.server.input.InputManagerInternal; +import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; import com.android.server.power.feature.PowerManagerFlags; @@ -71,6 +83,8 @@ import java.util.concurrent.Executor; public class NotifierTest { private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent"; private static final int USER_ID = 0; + private static final int DISPLAY_PORT = 0xFF; + private static final long DISPLAY_MODEL = 0xEEEEEEEEL; @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock; @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock; @@ -81,10 +95,16 @@ public class NotifierTest { @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; @Mock private Vibrator mVibrator; @Mock private StatusBarManagerInternal mStatusBarManagerInternal; + @Mock private InputManagerInternal mInputManagerInternal; + @Mock private InputMethodManagerInternal mInputMethodManagerInternal; + @Mock private DisplayManagerInternal mDisplayManagerInternal; + @Mock private ActivityManagerInternal mActivityManagerInternal; @Mock private WakeLockLog mWakeLockLog; @Mock private IBatteryStats mBatteryStats; + @Mock private WindowManagerPolicy mPolicy; + @Mock private PowerManagerFlags mPowerManagerFlags; @Mock private AppOpsManager mAppOpsManager; @@ -96,6 +116,8 @@ public class NotifierTest { private FakeExecutor mTestExecutor = new FakeExecutor(); private Notifier mNotifier; + private DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); + @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -103,11 +125,25 @@ public class NotifierTest { LocalServices.removeServiceForTest(StatusBarManagerInternal.class); LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternal); + LocalServices.removeServiceForTest(InputMethodManagerInternal.class); + LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal); + + LocalServices.removeServiceForTest(ActivityManagerInternal.class); + LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal); + + mDefaultDisplayInfo.address = DisplayAddress.fromPortAndModel(DISPLAY_PORT, DISPLAY_MODEL); + LocalServices.removeServiceForTest(DisplayManagerInternal.class); + LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal); + mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext())); mResourcesSpy = spy(mContextSpy.getResources()); when(mContextSpy.getResources()).thenReturn(mResourcesSpy); when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn(""); when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator); + when(mDisplayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn( + mDefaultDisplayInfo); mService = new PowerManagerService(mContextSpy, mInjector); } @@ -231,6 +267,32 @@ public class NotifierTest { verify(mStatusBarManagerInternal, never()).showChargingAnimation(anyInt()); } + @Test + public void testOnGlobalWakefulnessChangeStarted() throws Exception { + createNotifier(); + // GIVEN system is currently non-interactive + when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(false); + final int displayId1 = 101; + final int displayId2 = 102; + final int[] displayIds = new int[]{displayId1, displayId2}; + when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displayIds)); + mNotifier.onGlobalWakefulnessChangeStarted(WAKEFULNESS_ASLEEP, + PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, /* eventTime= */ 1000); + mTestLooper.dispatchAll(); + + // WHEN a global wakefulness change to interactive starts + mNotifier.onGlobalWakefulnessChangeStarted(WAKEFULNESS_AWAKE, + PowerManager.WAKE_REASON_TAP, /* eventTime= */ 2000); + mTestLooper.dispatchAll(); + + // THEN input is notified of all displays being interactive + final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray(); + expectedDisplayInteractivities.put(displayId1, true); + expectedDisplayInteractivities.put(displayId2, true); + verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities); + verify(mInputMethodManagerInternal).setInteractive(/* interactive= */ true); + } + @Test public void testOnWakeLockListener_RemoteException_NoRethrow() throws RemoteException { when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); @@ -551,7 +613,7 @@ public class NotifierTest { mContextSpy, mBatteryStats, mInjector.createSuspendBlocker(mService, "testBlocker"), - null, + mPolicy, null, null, mTestExecutor, mPowerManagerFlags, injector); diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index b58c28bfbe62e51c0b37e8e1db5a0970d60115d5..54a02cf3cda35d99afa17d4c892c12d80f9ca796 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -268,6 +268,10 @@ public class PowerManagerServiceTest { mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); + DisplayInfo displayInfo = Mockito.mock(DisplayInfo.class); + displayInfo.displayGroupId = Display.DEFAULT_DISPLAY_GROUP; + when(mDisplayManagerInternalMock.getDisplayInfo(Display.DEFAULT_DISPLAY)) + .thenReturn(displayInfo); } private PowerManagerService createService() { @@ -794,6 +798,57 @@ public class PowerManagerServiceTest { assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); } + @Test + public void testWakefulnessPerGroup_IPowerManagerWakeUpWithDisplayId() { + final int nonDefaultPowerGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; + int displayInNonDefaultGroup = 1; + final AtomicReference listener = + new AtomicReference<>(); + long eventTime1 = 10; + long eventTime2 = eventTime1 + 1; + long eventTime3 = eventTime2 + 1; + doAnswer((Answer) invocation -> { + listener.set(invocation.getArgument(0)); + return null; + }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any()); + + createService(); + startSystem(); + listener.get().onDisplayGroupAdded(nonDefaultPowerGroupId); + + // Verify the global wakefulness is AWAKE + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + // Transition default display to doze, and verify the global wakefulness + mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_DOZING, eventTime1, + 0, PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE, 0, null, null); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + + // Transition the display from non default power group to doze, and verify the change in + // the global wakefulness + mService.setWakefulnessLocked(nonDefaultPowerGroupId, WAKEFULNESS_DOZING, eventTime2, + 0, PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING); + assertThat(mService.getWakefulnessLocked(nonDefaultPowerGroupId)) + .isEqualTo(WAKEFULNESS_DOZING); + + // Wakeup the display from the non default power group + DisplayInfo displayInfo = Mockito.mock(DisplayInfo.class); + displayInfo.displayGroupId = nonDefaultPowerGroupId; + when(mDisplayManagerInternalMock.getDisplayInfo(displayInNonDefaultGroup)) + .thenReturn(displayInfo); + mClock.fastForward(eventTime3); + mService.getBinderServiceInstance().wakeUpWithDisplayId(eventTime3, + PowerManager.WAKE_REASON_APPLICATION, "testing IPowerManager.wakeUp()", + "pkg.name", displayInNonDefaultGroup); + + assertThat(mService.getWakefulnessLocked(nonDefaultPowerGroupId)) + .isEqualTo(WAKEFULNESS_AWAKE); + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY)) + .isEqualTo(WAKEFULNESS_DOZING); + } + /** * Tests a series of variants that control whether a device wakes-up when it is plugged in * or docked. diff --git a/services/tests/powerstatstests/res/xml/irq_device_map_3.xml b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml index fd55428c48dfc42cf6d121e75970dfb26cbf87b4..c3df0785bd9be778dc2947d7bced24d1e524b04b 100644 --- a/services/tests/powerstatstests/res/xml/irq_device_map_3.xml +++ b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml @@ -32,4 +32,7 @@ Sensor + + Bluetooth + \ No newline at end of file diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java index 1d20538724a8c31a67d6de4f8ee88df445d28a14..c037f97e34c936177d87326483a70a4cc94f4a28 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -927,7 +927,7 @@ public class BatteryStatsImplTest { assertThat(mPowerStatsStore.getTableOfContents()).isEmpty(); mBatteryStatsImpl.saveBatteryUsageStatsOnReset(mBatteryUsageStatsProvider, - mPowerStatsStore); + mPowerStatsStore, /* accumulateBatteryUsageStats */ false); synchronized (mBatteryStatsImpl) { mBatteryStatsImpl.noteFlashlightOnLocked(42, mMockClock.realtime, mMockClock.uptime); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java index fde84e967c98ee0741def83fbec9e3d3c0e7c45a..0e60156aecd18e57c3a962f11cb1e9326e1799de 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java @@ -419,7 +419,8 @@ public class BatteryUsageStatsProviderTest { mock(PowerAttributor.class), mStatsRule.getPowerProfile(), mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock); - batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore); + batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore, + /* accumulateBatteryUsageStats */ false); synchronized (batteryStats) { batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); } @@ -505,6 +506,102 @@ public class BatteryUsageStatsProviderTest { .of(180.0); } + @Test + public void accumulateBatteryUsageStats() { + BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats(); + + setTime(5 * MINUTE_IN_MS); + + // Capture the session start timestamp + synchronized (batteryStats) { + batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + PowerStatsStore powerStatsStore = new PowerStatsStore( + new File(mStatsRule.getHistoryDir(), getClass().getSimpleName()), + mStatsRule.getHandler()); + powerStatsStore.reset(); + + BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(mContext, + mock(PowerAttributor.class), mStatsRule.getPowerProfile(), + mStatsRule.getCpuScalingPolicies(), powerStatsStore, mMockClock); + + batteryStats.saveBatteryUsageStatsOnReset(provider, powerStatsStore, + /* accumulateBatteryUsageStats */ true); + + synchronized (batteryStats) { + batteryStats.noteFlashlightOnLocked(APP_UID, + 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS); + } + synchronized (batteryStats) { + batteryStats.noteFlashlightOffLocked(APP_UID, + 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS); + } + + synchronized (batteryStats) { + batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + synchronized (batteryStats) { + batteryStats.noteFlashlightOnLocked(APP_UID, + 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS); + } + synchronized (batteryStats) { + batteryStats.noteFlashlightOffLocked(APP_UID, + 50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS); + } + setTime(55 * MINUTE_IN_MS); + synchronized (batteryStats) { + batteryStats.resetAllStatsAndHistoryLocked(BatteryStatsImpl.RESET_REASON_ADB_COMMAND); + } + + // This section has not been saved yet, but should be added to the accumulated totals + synchronized (batteryStats) { + batteryStats.noteFlashlightOnLocked(APP_UID, + 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS); + } + synchronized (batteryStats) { + batteryStats.noteFlashlightOffLocked(APP_UID, + 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS); + } + setTime(115 * MINUTE_IN_MS); + + // Await completion + ConditionVariable done = new ConditionVariable(); + mStatsRule.getHandler().post(done::open); + done.block(); + + BatteryUsageStats stats = provider.getBatteryUsageStats(batteryStats, + new BatteryUsageStatsQuery.Builder().accumulated().build()); + + assertThat(stats.getStatsStartTimestamp()).isEqualTo(5 * MINUTE_IN_MS); + assertThat(stats.getStatsEndTimestamp()).isEqualTo(115 * MINUTE_IN_MS); + + // Section 1 (saved): 20 - 10 = 10 + // Section 2 (saved): 50 - 30 = 20 + // Section 3 (fresh): 110 - 80 = 30 + // Total: 10 + 20 + 30 = 60 + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.0001) + .of(360.0); // 360 mA * 1.0 hour + assertThat(stats.getAggregateBatteryConsumer( + BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE) + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo(60 * MINUTE_IN_MS); + + final UidBatteryConsumer uidBatteryConsumer = stats.getUidBatteryConsumers().stream() + .filter(uid -> uid.getUid() == APP_UID).findFirst().get(); + assertThat(uidBatteryConsumer + .getConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isWithin(0.1) + .of(360.0); + assertThat(uidBatteryConsumer + .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) + .isEqualTo(60 * MINUTE_IN_MS); + } + private void setTime(long timeMs) { mMockClock.currentTime = timeMs; mMockClock.realtime = timeMs; @@ -550,7 +647,8 @@ public class BatteryUsageStatsProviderTest { return null; }).when(powerStatsStore).storeBatteryUsageStats(anyLong(), any()); - mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore); + mStatsRule.getBatteryStats().saveBatteryUsageStatsOnReset(provider, powerStatsStore, + /* accumulateBatteryUsageStats */ false); // Make an incompatible change of supported energy components. This will trigger // a BatteryStats reset, which will generate a snapshot of battery stats. diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java index 0dc836ba04009560e8f72567bbeb8a2eb21adeb3..fe4d971face518bddafb35c0ea4ddb3375e6e9b9 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java @@ -17,6 +17,7 @@ package com.android.server.power.stats.wakeups; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_BLUETOOTH; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; @@ -52,6 +53,7 @@ public class CpuWakeupStatsTest { private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device"; private static final String KERNEL_REASON_SENSOR_IRQ = "15 test.sensor.device"; private static final String KERNEL_REASON_CELLULAR_DATA_IRQ = "18 test.cellular_data.device"; + private static final String KERNEL_REASON_BLUETOOTH_IRQ = "19 test.bluetooth.device"; private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device"; private static final String KERNEL_REASON_UNKNOWN_FORMAT = "free-form-reason test.alarm.device"; private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device"; @@ -62,12 +64,14 @@ public class CpuWakeupStatsTest { private static final int TEST_UID_3 = 92261423; private static final int TEST_UID_4 = 56926423; private static final int TEST_UID_5 = 76421423; + private static final int TEST_UID_6 = 62345353; private static final int TEST_PROC_STATE_1 = 72331; private static final int TEST_PROC_STATE_2 = 792351; private static final int TEST_PROC_STATE_3 = 138831; private static final int TEST_PROC_STATE_4 = 23231; private static final int TEST_PROC_STATE_5 = 42; + private static final int TEST_PROC_STATE_6 = 129942; private static final Context sContext = InstrumentationRegistry.getTargetContext(); private final Handler mHandler = Mockito.mock(Handler.class); @@ -79,6 +83,7 @@ public class CpuWakeupStatsTest { obj.mUidProcStates.put(TEST_UID_3, TEST_PROC_STATE_3); obj.mUidProcStates.put(TEST_UID_4, TEST_PROC_STATE_4); obj.mUidProcStates.put(TEST_UID_5, TEST_PROC_STATE_5); + obj.mUidProcStates.put(TEST_UID_6, TEST_PROC_STATE_6); } @Test @@ -118,6 +123,7 @@ public class CpuWakeupStatsTest { CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, CPU_WAKEUP_SUBSYSTEM_SENSOR, CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA, + CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, }; final String[] kernelReasons = new String[] { @@ -126,10 +132,11 @@ public class CpuWakeupStatsTest { KERNEL_REASON_SOUND_TRIGGER_IRQ, KERNEL_REASON_SENSOR_IRQ, KERNEL_REASON_CELLULAR_DATA_IRQ, + KERNEL_REASON_BLUETOOTH_IRQ, }; final int[] uids = new int[] { - TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5 + TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5, TEST_UID_6 }; final int[] procStates = new int[] { @@ -137,7 +144,8 @@ public class CpuWakeupStatsTest { TEST_PROC_STATE_3, TEST_PROC_STATE_4, TEST_PROC_STATE_1, - TEST_PROC_STATE_5 + TEST_PROC_STATE_5, + TEST_PROC_STATE_6 }; final int total = subsystems.length; @@ -284,6 +292,40 @@ public class CpuWakeupStatsTest { TEST_PROC_STATE_5); } + @Test + public void bluetoothIrqAttributionSolo() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); + final long wakeupTime = 1236121; + + populateDefaultProcStates(obj); + + obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_BLUETOOTH_IRQ); + + // Outside the window, so should be ignored. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, + wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, + wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2); + // Should be attributed + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime + 5, TEST_UID_3, + TEST_UID_5); + + final SparseArray attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution).isNotNull(); + assertThat(attribution.size()).isEqualTo(1); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)).isTrue(); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey( + TEST_UID_1)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey( + TEST_UID_2)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_3)).isEqualTo( + TEST_PROC_STATE_3); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey( + TEST_UID_4)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_5)).isEqualTo( + TEST_PROC_STATE_5); + } + @Test public void alarmAndWifiIrqAttribution() { final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); @@ -399,6 +441,47 @@ public class CpuWakeupStatsTest { assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isFalse(); } + @Test + public void unknownAndBluetoothAttribution() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); + final long wakeupTime = 92123520; + + populateDefaultProcStates(obj); + + obj.noteWakeupTimeAndReason(wakeupTime, 24, + KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_BLUETOOTH_IRQ); + + // Bluetooth activity + // Outside the window, so should be ignored. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, + wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_4); + // Should be attributed + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime + 2, TEST_UID_1); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime - 1, TEST_UID_3, + TEST_UID_5); + + // Unrelated, should be ignored. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3); + + final SparseArray attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution).isNotNull(); + assertThat(attribution.size()).isEqualTo(2); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)).isTrue(); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_1)).isEqualTo( + TEST_PROC_STATE_1); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH) + .indexOfKey(TEST_UID_2)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_3)).isEqualTo( + TEST_PROC_STATE_3); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH) + .indexOfKey(TEST_UID_4)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_5)).isEqualTo( + TEST_PROC_STATE_5); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue(); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isNull(); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isFalse(); + } + @Test public void unknownFormatWakeupIgnored() { final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index bbe0755b9cc9e6c76e2c17dd1977ba7e7e29acb2..cbe6700f4d41337bc24a4d0794b43860e0417c2a 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -52,8 +52,10 @@ android_test { "services.credentials", "services.devicepolicy", "services.flags", + "com.android.server.flags.services-aconfig-java", "services.net", "services.people", + "services.supervision", "services.usage", "service-permission.stubs.system_server", "guava", @@ -80,6 +82,7 @@ android_test { // TODO: remove once Android migrates to JUnit 4.12, // which provides assertThrows "testng", + "flag-junit", "junit", "junit-params", "ActivityContext", diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java index ec78bcea7539688687425f0bf5195027e157d6bd..c18faef2c0285814dbd631998eaa1107911c2259 100644 --- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -31,6 +31,9 @@ import android.content.pm.ResolveInfo; import android.os.Binder; import android.os.Handler; import android.os.Looper; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; import android.testing.TestableContext; @@ -43,6 +46,9 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.flags.Flags; +import com.android.server.pinner.PinnedFile; +import com.android.server.pinner.PinnerService; import com.android.server.testutils.FakeDeviceConfigInterface; import com.android.server.wm.ActivityTaskManagerInternal; @@ -73,15 +79,18 @@ public class PinnerServiceTest { private static final long WAIT_FOR_PINNER_TIMEOUT = TimeUnit.SECONDS.toMillis(2); + private static final int MEMORY_PERCENTAGE_FOR_QUOTA = 10; + @Rule public TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext(), null); + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final ArraySet mUpdatedPackages = new ArraySet<>(); private ResolveInfo mHomePackageResolveInfo; private FakeDeviceConfigInterface mFakeDeviceConfigInterface; private PinnerService.Injector mInjector; - @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -114,6 +123,8 @@ public class PinnerServiceTest { resources.addOverride(com.android.internal.R.bool.config_pinnerCameraApp, false); resources.addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 0); resources.addOverride(com.android.internal.R.bool.config_pinnerAssistantApp, false); + resources.addOverride(com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, + MEMORY_PERCENTAGE_FOR_QUOTA); mFakeDeviceConfigInterface = new FakeDeviceConfigInterface(); setDeviceConfigPinnedAnonSize(0); @@ -138,10 +149,9 @@ public class PinnerServiceTest { } @Override - protected PinnerService.PinnedFile pinFileInternal(String fileToPin, - int maxBytesToPin, boolean attemptPinIntrospection) { - return new PinnerService.PinnedFile(-1, - maxBytesToPin, fileToPin, maxBytesToPin); + protected PinnedFile pinFileInternal(PinnerService service, String fileToPin, + long maxBytesToPin, boolean attemptPinIntrospection) { + return new PinnedFile(-1, maxBytesToPin, fileToPin, maxBytesToPin); } }; } @@ -167,6 +177,12 @@ public class PinnerServiceTest { unpinAnonRegionMethod.invoke(pinnerService); } + private long getGlobalPinQuota(PinnerService service) throws Exception { + Method getQuotaMethod = PinnerService.class.getDeclaredMethod("getAvailableGlobalQuota"); + getQuotaMethod.setAccessible(true); + return (long) getQuotaMethod.invoke(service); + } + private void waitForPinnerService(PinnerService pinnerService) throws NoSuchFieldException, IllegalAccessException { // There's no notification/callback when pinning finished @@ -315,14 +331,120 @@ public class PinnerServiceTest { PinnerService pinnerService = new PinnerService(mContext, mInjector); pinnerService.onStart(); - pinnerService.pinFile("test_file", 4096, null, "my_group"); + pinnerService.pinFile("test_file", 4096, null, "my_group", false); - assertThat(getPinnedSize(pinnerService)).isGreaterThan(0); - assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0); + assertThat(getPinnedSize(pinnerService)).isEqualTo(4096); + assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(1); + + unpinAll(pinnerService); + } + + @Test + @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA) + public void testPinAllQuota() throws Exception { + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + + long quota = getGlobalPinQuota(pinnerService); + + pinnerService.pinFile("test_file", Long.MAX_VALUE, null, "my_group", false); + + assertThat(getPinnedSize(pinnerService)).isEqualTo(quota); unpinAll(pinnerService); } + @Test + @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA) + public void testGlobalPinQuotaAsDevicePercentage() throws Exception { + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + long origQuota = getGlobalPinQuota(pinnerService); + + long totalMem = android.os.Process.getTotalMemory(); + + // Verify that pin quota is the set percentage of device total memory + assertThat(origQuota).isEqualTo((totalMem * MEMORY_PERCENTAGE_FOR_QUOTA) / 100); + + pinnerService.pinFile("test_file", 4096, null, "my_group", false); + assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota - 4096); + } + + @Test + @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA) + public void testGlobalPinWhenNoQuota() throws Exception { + TestableResources resources = mContext.getOrCreateTestableResources(); + resources.addOverride( + com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0); + + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + + // Verify that pin quota is zero + assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0); + + pinnerService.pinFile("test_file", 4096, null, "my_group", false); + assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(0); + } + + /** + * This test is temporary, it should be cleaned up when removing the pin_global_quota bugfix + * flag. + */ + @Test + @DisableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA) + public void testGlobalQuotaDisabled() throws Exception { + TestableResources resources = mContext.getOrCreateTestableResources(); + resources.addOverride( + com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0); + + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + + // The quota parameter exists but it should have no effect on pinning + long quota = getGlobalPinQuota(pinnerService); + + pinnerService.pinFile("test_file", quota + 1, null, "my_group", false); + + // Verify that we can pin past the quota as it is disabled + assertThat(getPinnedSize(pinnerService)).isEqualTo(quota + 1); + } + + @Test + @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA) + public void testUnpinReleasesQuota() throws Exception { + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + long origQuota = getGlobalPinQuota(pinnerService); + + // Verify that pin quota exists and is non zero. + assertThat(getGlobalPinQuota(pinnerService)).isGreaterThan(0); + + pinnerService.pinFile("test_file", origQuota, null, "my_group", false); + + // Make sure all the quota was consumed + assertThat(getPinnedSize(pinnerService)).isEqualTo(origQuota); + + // Unpin the file and verify that the quota has been released. + pinnerService.unpinFile("test_file"); + assertThat(getPinnedSize(pinnerService)).isEqualTo(0); + assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota); + } + + @Test + @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA) + public void testGlobalPinQuotaNegative() throws Exception { + TestableResources resources = mContext.getOrCreateTestableResources(); + resources.addOverride( + com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, -10); + + PinnerService pinnerService = new PinnerService(mContext, mInjector); + pinnerService.onStart(); + + // Verify that pin quota is zero + assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0); + } + @Test public void testPinAnonRegion() throws Exception { setDeviceConfigPinnedAnonSize(32768); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 6e6d5a870031f9f22f3585f0632addbcc43464fc..8dfd54fe38bc4f2056a7092b0864e27c3997115f 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -174,8 +174,8 @@ public class AbstractAccessibilityServiceConnectionTest { @Mock private AccessibilityTrace mMockA11yTrace; @Mock private WindowManagerInternal mMockWindowManagerInternal; @Mock private SystemActionPerformer mMockSystemActionPerformer; - @Mock private IBinder mMockService; - @Mock private IAccessibilityServiceClient mMockServiceInterface; + @Mock private IBinder mMockClientBinder; + @Mock private IAccessibilityServiceClient mMockClient; @Mock private KeyEventDispatcher mMockKeyEventDispatcher; @Mock private IAccessibilityInteractionConnection mMockIA11yInteractionConnection; @Mock private IAccessibilityInteractionConnectionCallback mMockCallback; @@ -247,9 +247,9 @@ public class AbstractAccessibilityServiceConnectionTest { mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal, mMockSystemActionPerformer, mMockA11yWindowManager); - // Assume that the service is connected - mServiceConnection.mService = mMockService; - mServiceConnection.mServiceInterface = mMockServiceInterface; + // Assume that the client is connected + mServiceConnection.mClientBinder = mMockClientBinder; + mServiceConnection.mClient = mMockClient; // Update security policy for this service when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true); @@ -273,7 +273,7 @@ public class AbstractAccessibilityServiceConnectionTest { final KeyEvent mockKeyEvent = mock(KeyEvent.class); mServiceConnection.onKeyEvent(mockKeyEvent, sequenceNumber); - verify(mMockServiceInterface).onKeyEvent(mockKeyEvent, sequenceNumber); + verify(mMockClient).onKeyEvent(mockKeyEvent, sequenceNumber); } @Test diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index 566feb7e3d80c8b655f90f65106e782ba3318812..7481fc8ec46d93c142ba0ebf6198fd9edf22215b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -63,8 +63,11 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; +import android.annotation.NonNull; import android.app.PendingIntent; import android.app.RemoteAction; +import android.app.admin.DevicePolicyManager; +import android.app.ecm.EnhancedConfirmationManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -212,6 +215,7 @@ public class AccessibilityManagerServiceTest { @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController; @Mock private ProxyManager mProxyManager; @Mock private StatusBarManagerInternal mStatusBarManagerInternal; + @Mock private DevicePolicyManager mDevicePolicyManager; @Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback; @Captor private ArgumentCaptor mIntentArgumentCaptor; private IAccessibilityManager mA11yManagerServiceOnDevice; @@ -241,6 +245,7 @@ public class AccessibilityManagerServiceTest { UserManagerInternal.class, mMockUserManagerInternal); LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal); mInputFilter = mock(FakeInputFilter.class); + mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager); when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn( mMockMagnificationConnectionManager); @@ -2160,6 +2165,24 @@ public class AccessibilityManagerServiceTest { .isEqualTo(SOFTWARE); } + @Test + @EnableFlags({android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED, + android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS}) + public void isAccessibilityTargetAllowed_nonSystemUserId_useEcmWithNonSystemUserId() { + String fakePackageName = "FAKE_PACKAGE_NAME"; + int uid = 0; // uid is not used in the actual implementation when flags are on + int userId = mTestableContext.getUserId() + 1234; + when(mDevicePolicyManager.getPermittedAccessibilityServices(userId)).thenReturn( + List.of(fakePackageName)); + Context mockUserContext = mock(Context.class); + mTestableContext.addMockUserContext(userId, mockUserContext); + + mA11yms.isAccessibilityTargetAllowed(fakePackageName, uid, userId); + + verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class); + } + + private Set readStringsFromSetting(String setting) { final Set result = new ArraySet<>(); mA11yms.readColonDelimitedSettingToSet( @@ -2280,6 +2303,7 @@ public class AccessibilityManagerServiceTest { private final Context mMockContext; private final Map> mBroadcastReceivers = new ArrayMap<>(); + private ArrayMap mMockUserContexts = new ArrayMap<>(); A11yTestableContext(Context base) { super(base); @@ -2317,6 +2341,19 @@ public class AccessibilityManagerServiceTest { return mMockContext; } + public void addMockUserContext(int userId, Context context) { + mMockUserContexts.put(userId, context); + } + + @Override + @NonNull + public Context createContextAsUser(UserHandle user, int flags) { + if (mMockUserContexts.containsKey(user.getIdentifier())) { + return mMockUserContexts.get(user.getIdentifier()); + } + return super.createContextAsUser(user, flags); + } + Map> getBroadcastReceivers() { return mBroadcastReceivers; } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt index 019ccf93fa11fc3fefe8d296abb487b5de1cf9b3..c76392b302760b8bd1d8656894908b5e3c29b9cb 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt +++ b/services/tests/servicestests/src/com/android/server/accessibility/MouseKeysInterceptorTest.kt @@ -61,7 +61,6 @@ class MouseKeysInterceptorTest { companion object { const val DISPLAY_ID = 1 const val DEVICE_ID = 123 - const val MOUSE_POINTER_MOVEMENT_STEP = 1.8f // This delay is required for key events to be sent and handled correctly. // The handler only performs a move/scroll event if it receives the key event // at INTERVAL_MILLIS (which happens in practice). Hence, we need this delay in the tests. @@ -159,8 +158,8 @@ class MouseKeysInterceptorTest { testLooper.dispatchAll() // Verify the sendRelativeEvent method is called once and capture the arguments - verifyRelativeEvents(arrayOf(-MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)), - arrayOf(MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f))) + verifyRelativeEvents(arrayOf(-MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f)), + arrayOf(MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP / sqrt(2.0f))) } @Test @@ -232,7 +231,8 @@ class MouseKeysInterceptorTest { testLooper.dispatchAll() // Verify the sendScrollEvent method is called once and capture the arguments - verifyScrollEvents(arrayOf(0f), arrayOf(1.0f)) + verifyScrollEvents(arrayOf(0f), + arrayOf(MouseKeysInterceptor.MOUSE_SCROLL_STEP)) } @Test @@ -247,7 +247,8 @@ class MouseKeysInterceptorTest { testLooper.dispatchAll() // Verify the sendRelativeEvent method is called once and capture the arguments - verifyRelativeEvents(arrayOf(0f), arrayOf(-MOUSE_POINTER_MOVEMENT_STEP)) + verifyRelativeEvents(arrayOf(0f), + arrayOf(-MouseKeysInterceptor.MOUSE_POINTER_MOVEMENT_STEP)) } private fun verifyRelativeEvents(expectedX: Array, expectedY: Array) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 598d3a3a9f8a72a720fa39cf4cd223e9a2c2cee6..b745e6a7d4a5ae52ce386acbfbeee38cbe1968e0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -32,6 +32,7 @@ import static com.android.server.testutils.TestUtils.strictMock; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -64,6 +65,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.Handler; import android.os.Message; +import android.os.SystemClock; import android.os.UserHandle; import android.os.VibrationEffect; import android.os.Vibrator; @@ -105,6 +107,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.function.IntConsumer; @@ -699,6 +702,15 @@ public class FullScreenMagnificationGestureHandlerTest { returnToNormalFrom(STATE_ACTIVATED); } + @Test + public void testIntervalsOf_sendMotionEventInfo_returnMatchIntervals() { + FullScreenMagnificationGestureHandler.MotionEventInfo upEventQueue = + createEventQueue(ACTION_UP, 0, 100, 300); + + List upIntervals = mMgh.mDetectingState.intervalsOf(upEventQueue, ACTION_UP); + assertEquals(Arrays.asList(100L, 200L), upIntervals); + } + @Test public void testMagnifierDeactivates_shortcutTriggeredState_returnToIdleState() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); @@ -2294,6 +2306,31 @@ public class FullScreenMagnificationGestureHandlerTest { return event; } + private FullScreenMagnificationGestureHandler.MotionEventInfo createEventQueue( + int eventType, long... delays) { + FullScreenMagnificationGestureHandler.MotionEventInfo eventQueue = null; + long currentTime = SystemClock.uptimeMillis(); + + for (int i = 0; i < delays.length; i++) { + MotionEvent event = MotionEvent.obtain(currentTime + delays[i], + currentTime + delays[i], eventType, 0, 0, 0); + + FullScreenMagnificationGestureHandler.MotionEventInfo info = + FullScreenMagnificationGestureHandler.MotionEventInfo + .obtain(event, MotionEvent.obtain(event), 0); + + if (eventQueue == null) { + eventQueue = info; + } else { + FullScreenMagnificationGestureHandler.MotionEventInfo tail = eventQueue; + while (tail.getNext() != null) { + tail = tail.getNext(); + } + tail.setNext(info); + } + } + return eventQueue; + } private String stateDump() { return "\nCurrent state dump:\n" + mMgh + "\n" + mHandler.getPendingMessages(); diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index b9ce8ad0b0184bc7e578f3035f44fdc7c2739744..0c92abce72541ba879c32353eb610d8ce2f37d46 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -1163,6 +1163,16 @@ public class AccountManagerServiceTest extends AndroidTestCase { verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture()); Bundle result = mBundleCaptor.getValue(); + Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE); + assertNotNull(sessionBundle); + // Assert that session bundle is decrypted and hence data is visible. + assertEquals(AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1, + sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1)); + // Assert finishSessionAsUser added calling uid and pid into the sessionBundle + assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_UID)); + assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_PID)); + assertEquals(sessionBundle.getString( + AccountManager.KEY_ANDROID_PACKAGE_NAME), "APCT.package"); // Verify response data assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null)); @@ -2111,6 +2121,12 @@ public class AccountManagerServiceTest extends AndroidTestCase { result.getString(AccountManager.KEY_ACCOUNT_NAME)); assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1, result.getString(AccountManager.KEY_ACCOUNT_TYPE)); + + Bundle optionBundle = result.getParcelable( + AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE); + // Assert addAccountAsUser added calling uid and pid into the option bundle + assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_UID)); + assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_PID)); } @SmallTest @@ -3441,52 +3457,6 @@ public class AccountManagerServiceTest extends AndroidTestCase { + (readTotalTime.doubleValue() / readerCount / loopSize)); } - @SmallTest - public void testSanitizeBundle_expectedFields() throws Exception { - Bundle bundle = new Bundle(); - bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name"); - bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, "type"); - bundle.putString(AccountManager.KEY_AUTHTOKEN, "token"); - bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, "label"); - bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error message"); - bundle.putString(AccountManager.KEY_PASSWORD, "password"); - bundle.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, "status"); - - bundle.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 123L); - bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); - bundle.putInt(AccountManager.KEY_ERROR_CODE, 456); - - Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle); - assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name"); - assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_TYPE), "type"); - assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTHTOKEN), "token"); - assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTH_TOKEN_LABEL), "label"); - assertEquals(sanitizedBundle.getString(AccountManager.KEY_ERROR_MESSAGE), "error message"); - assertEquals(sanitizedBundle.getString(AccountManager.KEY_PASSWORD), "password"); - assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN), "status"); - - assertEquals(sanitizedBundle.getLong( - AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 0), 123L); - assertEquals(sanitizedBundle.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false), true); - assertEquals(sanitizedBundle.getInt(AccountManager.KEY_ERROR_CODE, 0), 456); - } - - @SmallTest - public void testSanitizeBundle_filtersUnexpectedFields() throws Exception { - Bundle bundle = new Bundle(); - bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name"); - bundle.putString("unknown_key", "value"); - Bundle sessionBundle = new Bundle(); - bundle.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); - - Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle); - - assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name"); - assertFalse(sanitizedBundle.containsKey("unknown_key")); - // It is a valid response from Authenticator which will be accessed using original Bundle - assertFalse(sanitizedBundle.containsKey(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE)); - } - private void waitForCyclicBarrier(CyclicBarrier cyclicBarrier) { try { cyclicBarrier.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS); diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java index 46451563299764b463c0e8f7849e320675a3fde3..3e2949d6018366895b28627d72e54648c03db8da 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java @@ -16,7 +16,9 @@ package com.android.server.audio; +import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX; import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; +import static com.android.media.audio.Flags.absVolumeIndexFix; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; @@ -116,7 +118,7 @@ public class AudioDeviceVolumeManagerTest { } @Test - @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) + @RequiresFlagsDisabled({FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME, FLAG_ABS_VOLUME_INDEX_FIX}) public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception { AudioManager am = mContext.getSystemService(AudioManager.class); final int minIndex = am.getStreamMinVolume(AudioManager.STREAM_MUSIC); @@ -177,6 +179,7 @@ public class AudioDeviceVolumeManagerTest { final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes( /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla"); final int maxPreScaleIndex = 3; + int passedIndex = maxIndex; for (int i = 0; i < maxPreScaleIndex; i++) { final VolumeInfo volCur = new VolumeInfo.Builder(volMedia) @@ -185,9 +188,12 @@ public class AudioDeviceVolumeManagerTest { mAudioService.setDeviceVolume(volCur, bleDevice, mPackageName); mTestLooper.dispatchAll(); + if (absVolumeIndexFix()) { + passedIndex = i + 1; + } // Stream volume changes verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, maxIndex, + AudioManager.STREAM_MUSIC, passedIndex, AudioSystem.DEVICE_OUT_BLE_HEADSET); } @@ -197,8 +203,11 @@ public class AudioDeviceVolumeManagerTest { mAudioService.setDeviceVolume(volIndex4, bleDevice, mPackageName); mTestLooper.dispatchAll(); + if (absVolumeIndexFix()) { + passedIndex = 4; + } verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - AudioManager.STREAM_MUSIC, maxIndex, + AudioManager.STREAM_MUSIC, passedIndex, AudioSystem.DEVICE_OUT_BLE_HEADSET); } } diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java index beed0a3d413c2ab26bd0eb4af64652f883a04c58..c305fd92cfbbf12e77d1026ac2c97c36cad6c720 100644 --- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java @@ -39,8 +39,9 @@ import static android.media.audio.Flags.autoPublicVolumeApiHardening; import static android.view.KeyEvent.ACTION_DOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_UP; -import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX; +import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME; +import static com.android.media.audio.Flags.absVolumeIndexFix; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -627,7 +628,6 @@ public class VolumeHelperTest { @Test @RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME) - @RequiresFlagsDisabled(FLAG_ABS_VOLUME_INDEX_FIX) public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception { final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC); final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC); @@ -638,6 +638,7 @@ public class VolumeHelperTest { final AudioDeviceAttributes bleDevice = new AudioDeviceAttributes( /*native type*/ AudioSystem.DEVICE_OUT_BLE_HEADSET, /*address*/ "bla"); final int maxPreScaleIndex = 3; + int passedIndex = maxIndex; for (int i = 0; i < maxPreScaleIndex; i++) { final VolumeInfo volCur = new VolumeInfo.Builder(volMedia) @@ -646,9 +647,12 @@ public class VolumeHelperTest { mAudioService.setDeviceVolume(volCur, bleDevice, mContext.getOpPackageName()); mTestLooper.dispatchAll(); + if (absVolumeIndexFix()) { + passedIndex = i + 1; + } // Stream volume changes verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - STREAM_MUSIC, maxIndex, + STREAM_MUSIC, passedIndex, AudioSystem.DEVICE_OUT_BLE_HEADSET); } @@ -658,8 +662,11 @@ public class VolumeHelperTest { mAudioService.setDeviceVolume(volIndex4, bleDevice, mContext.getOpPackageName()); mTestLooper.dispatchAll(); + if (absVolumeIndexFix()) { + passedIndex = 4; + } verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( - STREAM_MUSIC, maxIndex, + STREAM_MUSIC, passedIndex, AudioSystem.DEVICE_OUT_BLE_HEADSET); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java index 14cb22d7698e8ce67cd1c391f932dbc91d9bc887..efc2d974a7cc0a7dcbc6cc495f9eb6fef8a0d5a5 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java @@ -16,12 +16,20 @@ package com.android.server.biometrics; +import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED; import static android.hardware.biometrics.BiometricManager.Authenticators; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; + +import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; @@ -36,8 +44,12 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; @Presubmit @SmallTest @@ -45,6 +57,17 @@ public class UtilsTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @Rule + public MockitoRule mockitorule = MockitoJUnit.rule(); + + @Mock + private Context mContext; + + @Before + public void setUp() { + doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission( + eq(SET_BIOMETRIC_DIALOG_ADVANCED), any()); + } @Test public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() { @@ -162,28 +185,39 @@ public class UtilsTest { @Test public void testIsValidAuthenticatorConfig() { - assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.EMPTY_SET)); + assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.EMPTY_SET)); - assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_STRONG)); + assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_STRONG)); - assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_WEAK)); + assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_WEAK)); - assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL)); + assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL)); - assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL + assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG)); - assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL + assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK)); - assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE)); + assertFalse(Utils.isValidAuthenticatorConfig( + mContext, Authenticators.BIOMETRIC_CONVENIENCE)); - assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE + assertFalse(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_CONVENIENCE | Authenticators.DEVICE_CREDENTIAL)); - assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MAX_STRENGTH)); + assertFalse(Utils.isValidAuthenticatorConfig( + mContext, Authenticators.BIOMETRIC_MAX_STRENGTH)); + + assertFalse(Utils.isValidAuthenticatorConfig( + mContext, Authenticators.BIOMETRIC_MIN_STRENGTH)); + + assertThrows(SecurityException.class, () -> Utils.isValidAuthenticatorConfig( + mContext, Authenticators.MANDATORY_BIOMETRICS)); + + doNothing().when(mContext).enforceCallingOrSelfPermission( + eq(SET_BIOMETRIC_DIALOG_ADVANCED), any()); - assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MIN_STRENGTH)); + assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.MANDATORY_BIOMETRICS)); // The rest of the bits are not allowed to integrate with the public APIs for (int i = 8; i < 32; i++) { @@ -192,7 +226,7 @@ public class UtilsTest { || authenticator == Authenticators.MANDATORY_BIOMETRICS) { continue; } - assertFalse(Utils.isValidAuthenticatorConfig(1 << i)); + assertFalse(Utils.isValidAuthenticatorConfig(mContext, 1 << i)); } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java index d071c159d6f5b5c1c6635873b98b281d91c51ee1..ae781dcb834a12ec20bc8bc01cef9baf210a4a5a 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java @@ -60,6 +60,7 @@ import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -130,6 +131,7 @@ public class RebootEscrowManagerTests { private SecretKey mAesKey; private MockInjector mMockInjector; private Handler mHandler; + private Network mNetwork; public interface MockableRebootEscrowInjected { int getBootCount(); @@ -342,6 +344,7 @@ public class RebootEscrowManagerTests { when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false); when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true); mInjected = mock(MockableRebootEscrowInjected.class); + mNetwork = mock(Network.class); mMockInjector = new MockInjector( mContext, @@ -351,6 +354,10 @@ public class RebootEscrowManagerTests { mKeyStoreManager, mStorage, mInjected); + mMockInjector.mNetworkConsumer = + (callback) -> { + callback.onAvailable(mNetwork); + }; HandlerThread thread = new HandlerThread("RebootEscrowManagerTest"); thread.start(); mHandler = new Handler(thread.getLooper()); @@ -367,6 +374,10 @@ public class RebootEscrowManagerTests { mKeyStoreManager, mStorage, mInjected); + mMockInjector.mNetworkConsumer = + (callback) -> { + callback.onAvailable(mNetwork); + }; mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler); } @@ -621,7 +632,7 @@ public class RebootEscrowManagerTests { // pretend reboot happens here when(mInjected.getBootCount()).thenReturn(1); - mService.loadRebootEscrowDataIfAvailable(null); + mService.loadRebootEscrowDataIfAvailable(mHandler); verify(mServiceConnection, never()).unwrap(any(), anyLong()); verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt()); } @@ -678,7 +689,7 @@ public class RebootEscrowManagerTests { when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - mService.loadRebootEscrowDataIfAvailable(null); + mService.loadRebootEscrowDataIfAvailable(mHandler); verify(mServiceConnection).unwrap(any(), anyLong()); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); @@ -734,7 +745,7 @@ public class RebootEscrowManagerTests { when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - mService.loadRebootEscrowDataIfAvailable(null); + mService.loadRebootEscrowDataIfAvailable(mHandler); verify(mServiceConnection).unwrap(any(), anyLong()); verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID)); @@ -783,7 +794,7 @@ public class RebootEscrowManagerTests { when(mServiceConnection.unwrap(any(), anyLong())) .thenAnswer(invocation -> invocation.getArgument(0)); - mService.loadRebootEscrowDataIfAvailable(null); + mService.loadRebootEscrowDataIfAvailable(mHandler); verify(mServiceConnection).unwrap(any(), anyLong()); assertTrue(metricsSuccessCaptor.getValue()); verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); @@ -827,7 +838,7 @@ public class RebootEscrowManagerTests { anyInt()); when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class); - mService.loadRebootEscrowDataIfAvailable(null); + mService.loadRebootEscrowDataIfAvailable(mHandler); verify(mServiceConnection).unwrap(any(), anyLong()); assertFalse(metricsSuccessCaptor.getValue()); assertEquals( @@ -836,6 +847,7 @@ public class RebootEscrowManagerTests { } @Test + @RequiresFlagsDisabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_ServerBasedIoError_RetryFailure() throws Exception { setServerBasedRebootEscrowProvider(); @@ -928,114 +940,6 @@ public class RebootEscrowManagerTests { verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); } - @Test - @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) - public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternet_success() - throws Exception { - setServerBasedRebootEscrowProvider(); - - when(mInjected.getBootCount()).thenReturn(0); - RebootEscrowListener mockListener = mock(RebootEscrowListener.class); - mService.setRebootEscrowListener(mockListener); - mService.prepareRebootEscrow(); - - clearInvocations(mServiceConnection); - callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); - verify(mockListener).onPreparedForReboot(eq(true)); - verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); - - // Use x -> x for both wrap & unwrap functions. - when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) - .thenAnswer(invocation -> invocation.getArgument(0)); - assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); - verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); - assertTrue(mStorage.hasRebootEscrowServerBlob()); - - // pretend reboot happens here - when(mInjected.getBootCount()).thenReturn(1); - ArgumentCaptor metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); - doNothing() - .when(mInjected) - .reportMetric( - metricsSuccessCaptor.capture(), - eq(0) /* error code */, - eq(2) /* Server based */, - eq(1) /* attempt count */, - anyInt(), - eq(0) /* vbmeta status */, - anyInt()); - - // load escrow data - when(mServiceConnection.unwrap(any(), anyLong())) - .thenAnswer(invocation -> invocation.getArgument(0)); - Network mockNetwork = mock(Network.class); - mMockInjector.mNetworkConsumer = - (callback) -> { - callback.onAvailable(mockNetwork); - }; - - mService.loadRebootEscrowDataIfAvailable(mHandler); - verify(mServiceConnection).unwrap(any(), anyLong()); - assertTrue(metricsSuccessCaptor.getValue()); - verify(mKeyStoreManager).clearKeyStoreEncryptionKey(); - assertNull(mMockInjector.mNetworkCallback); - } - - @Test - @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) - public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternetRemoteException_Failure() - throws Exception { - setServerBasedRebootEscrowProvider(); - - when(mInjected.getBootCount()).thenReturn(0); - RebootEscrowListener mockListener = mock(RebootEscrowListener.class); - mService.setRebootEscrowListener(mockListener); - mService.prepareRebootEscrow(); - - clearInvocations(mServiceConnection); - callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID); - verify(mockListener).onPreparedForReboot(eq(true)); - verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong()); - - // Use x -> x for both wrap & unwrap functions. - when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong())) - .thenAnswer(invocation -> invocation.getArgument(0)); - assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded()); - verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong()); - assertTrue(mStorage.hasRebootEscrowServerBlob()); - - // pretend reboot happens here - when(mInjected.getBootCount()).thenReturn(1); - ArgumentCaptor metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class); - ArgumentCaptor metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class); - doNothing() - .when(mInjected) - .reportMetric( - metricsSuccessCaptor.capture(), - metricsErrorCodeCaptor.capture(), - eq(2) /* Server based */, - eq(1) /* attempt count */, - anyInt(), - eq(0) /* vbmeta status */, - anyInt()); - - // load escrow data - when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class); - Network mockNetwork = mock(Network.class); - mMockInjector.mNetworkConsumer = - (callback) -> { - callback.onAvailable(mockNetwork); - }; - - mService.loadRebootEscrowDataIfAvailable(mHandler); - verify(mServiceConnection).unwrap(any(), anyLong()); - assertFalse(metricsSuccessCaptor.getValue()); - assertEquals( - Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY), - metricsErrorCodeCaptor.getValue()); - assertNull(mMockInjector.mNetworkCallback); - } - @Test @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR) public void loadRebootEscrowDataIfAvailable_waitForInternet_networkUnavailable() diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index abc9ce3fdc366e8bd3a23404e722deead3e64bb9..ee63d5d32ff17fa01c2a663126c54db01822e4f8 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -38,6 +38,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -91,6 +92,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -174,8 +176,8 @@ public class MediaProjectionManagerServiceTest { private PackageManager mPackageManager; @Mock private KeyguardManager mKeyguardManager; - @Mock - AppOpsManager mAppOpsManager; + + private AppOpsManager mAppOpsManager; @Mock private IMediaProjectionWatcherCallback mWatcherCallback; @Mock @@ -193,6 +195,7 @@ public class MediaProjectionManagerServiceTest { LocalServices.removeServiceForTest(WindowManagerInternal.class); LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); + mAppOpsManager = mockAppOpsManager(); mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager); mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager); mContext.setMockPackageManager(mPackageManager); @@ -206,6 +209,17 @@ public class MediaProjectionManagerServiceTest { mService = new MediaProjectionManagerService(mContext); } + private static AppOpsManager mockAppOpsManager() { + return mock(AppOpsManager.class, invocationOnMock -> { + if (invocationOnMock.getMethod().getName().startsWith("noteOp")) { + // Mockito will return 0 for non-stubbed method which corresponds to MODE_ALLOWED + // and is not what we want. + return AppOpsManager.MODE_IGNORED; + } + return Answers.RETURNS_DEFAULTS.answer(invocationOnMock); + }); + } + @After public void tearDown() { LocalServices.removeServiceForTest(ActivityManagerInternal.class); @@ -305,8 +319,10 @@ public class MediaProjectionManagerServiceTest { public void testCreateProjection_keyguardLocked_AppOpMediaProjection() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); - doReturn(true).when(mAppOpsManager).isOperationActive(eq(AppOpsManager.OP_PROJECT_MEDIA), - eq(projection.uid), eq(projection.packageName)); + doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager) + .noteOpNoThrow(eq(AppOpsManager.OP_PROJECT_MEDIA), + eq(projection.uid), eq(projection.packageName), nullable(String.class), + nullable(String.class)); doReturn(true).when(mKeyguardManager).isKeyguardLocked(); doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission( @@ -1159,7 +1175,7 @@ public class MediaProjectionManagerServiceTest { doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(), any(ApplicationInfoFlags.class), any(UserHandle.class)); return service.createProjectionInternal(UID, PACKAGE_NAME, - TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT); + TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT); } // Set up preconditions for starting a projection, with no foreground service requirements. diff --git a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java index f91f77a563852492b681026631bf1d2e59b470d8..cdfc521dff13dfc73b2bfe676aa938b3da0ac133 100644 --- a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java @@ -86,7 +86,6 @@ public class PersistentDataBlockServiceTest { private File mDataBlockFile; private File mFrpSecretFile; private File mFrpSecretTmpFile; - private String mOemUnlockPropertyValue; private boolean mIsUpgradingFromPreV = false; @Mock private UserManager mUserManager; @@ -104,13 +103,6 @@ public class PersistentDataBlockServiceTest { signalInitDone(); } - @Override - void setProperty(String key, String value) { - // Override to capture the value instead of actually setting the property. - assertThat(key).isEqualTo("sys.oem_unlock_allowed"); - mOemUnlockPropertyValue = value; - } - @Override boolean isUpgradingFromPreVRelease() { return mIsUpgradingFromPreV; @@ -598,7 +590,6 @@ public class PersistentDataBlockServiceTest { mInterface.setOemUnlockEnabled(true); assertThat(mInterface.getOemUnlockEnabled()).isTrue(); - assertThat(mOemUnlockPropertyValue).isEqualTo("1"); } @Test @@ -635,7 +626,6 @@ public class PersistentDataBlockServiceTest { // The current implementation does not check digest before set or get the oem unlock bit. tamperWithDigest(); mInterface.setOemUnlockEnabled(true); - assertThat(mOemUnlockPropertyValue).isEqualTo("1"); tamperWithDigest(); assertThat(mInterface.getOemUnlockEnabled()).isTrue(); } @@ -676,7 +666,6 @@ public class PersistentDataBlockServiceTest { mInternalInterface.forceOemUnlockEnabled(true); - assertThat(mOemUnlockPropertyValue).isEqualTo("1"); assertThat(readBackingFile(mPdbService.getOemUnlockDataOffset(), 1).array()) .isEqualTo(new byte[] { 1 }); } diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..6bd4279152ab7f9f3b05b909a799791b2d387be7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.supervision + +import android.os.Bundle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.server.LocalServices +import com.android.server.pm.UserManagerInternal +import com.google.common.truth.Truth.assertThat +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +/** + * Unit tests for {@link SupervisionService}. + *

            + * Run with atest SupervisionServiceTest. + */ +@RunWith(AndroidJUnit4::class) +class SupervisionServiceTest { + companion object { + const val USER_ID = 100 + } + + private lateinit var service: SupervisionService + + @Rule + @JvmField + val mocks: MockitoRule = MockitoJUnit.rule() + + @Mock + private lateinit var mockUserManagerInternal: UserManagerInternal + + @Before + fun setup() { + val context = InstrumentationRegistry.getInstrumentation().context + + LocalServices.removeServiceForTest(UserManagerInternal::class.java) + LocalServices.addService(UserManagerInternal::class.java, mockUserManagerInternal) + + service = SupervisionService(context) + } + + @Test + fun testSetSupervisionEnabledForUser() { + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + + service.setSupervisionEnabledForUser(USER_ID, true) + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue() + + service.setSupervisionEnabledForUser(USER_ID, false) + assertThat(service.isSupervisionEnabledForUser(USER_ID)).isFalse() + } + + @Test + fun testSetSupervisionLockscreenEnabledForUser() { + var userData = service.getUserDataLocked(USER_ID) + assertThat(userData.supervisionLockScreenEnabled).isFalse() + assertThat(userData.supervisionLockScreenOptions).isNull() + + service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, true, Bundle()) + userData = service.getUserDataLocked(USER_ID) + assertThat(userData.supervisionLockScreenEnabled).isTrue() + assertThat(userData.supervisionLockScreenOptions).isNotNull() + + service.mInternal.setSupervisionLockscreenEnabledForUser(USER_ID, false, null) + userData = service.getUserDataLocked(USER_ID) + assertThat(userData.supervisionLockScreenEnabled).isFalse() + assertThat(userData.supervisionLockScreenOptions).isNull() + } +} diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java index b09e9b1199848f6adce47fd22eb5cf2ce1a17feb..54282ff7fadb2b331fe661ff7d66b064256c1ecf 100644 --- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java @@ -119,7 +119,7 @@ public class AnrTimerTest { */ private class TestInjector extends AnrTimer.Injector { @Override - boolean anrTimerServiceEnabled() { + boolean serviceEnabled() { return mEnabled; } } diff --git a/services/tests/timetests/Android.bp b/services/tests/timetests/Android.bp index aae6acc7c53a6a24bd5c8ea546231304f0a128f9..65a694e7c5facefb9796baf57c8885b846335063 100644 --- a/services/tests/timetests/Android.bp +++ b/services/tests/timetests/Android.bp @@ -19,6 +19,7 @@ android_test { "platform-test-annotations", "services.core", "truth", + "ApplicationSharedMemoryTestRule", ], libs: ["android.test.runner.stubs.system"], platform_apis: true, diff --git a/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java index c64ec724b6411cec5bfd041b734e4e258c1b3d2e..3836063a1b440f6c8af61cefbe20c5178b70ef96 100644 --- a/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java +++ b/services/tests/timetests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -46,6 +46,7 @@ import android.app.timedetector.TelephonyTimeSuggestion; import android.os.TimestampedValue; import android.util.IndentingPrintWriter; +import com.android.internal.os.ApplicationSharedMemoryTestRule; import com.android.server.SystemClockTime.TimeConfidence; import com.android.server.timedetector.TimeDetectorStrategy.Origin; import com.android.server.timezonedetector.StateChangeListener; @@ -55,6 +56,7 @@ import junitparams.JUnitParamsRunner; import junitparams.Parameters; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -125,6 +127,10 @@ public class TimeDetectorStrategyImplTest { .setAutoDetectionEnabledSetting(true) .build(); + @Rule + public final ApplicationSharedMemoryTestRule mApplicationSharedMemoryTestRule = + new ApplicationSharedMemoryTestRule(); + private FakeEnvironment mFakeEnvironment; private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy; private TimeDetectorStrategyImpl mTimeDetectorStrategy; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index b8f9767b5512289954338de5fc66e3f83734d622..bbf2cbdbc14580e1f0ba2fab1ea9571c724f2c3c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -38,6 +38,7 @@ import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_NO_DISMISS; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; +import static android.app.Notification.FLAG_PROMOTED_ONGOING; import static android.app.Notification.FLAG_USER_INITIATED_JOB; import static android.app.Notification.GROUP_ALERT_CHILDREN; import static android.app.Notification.VISIBILITY_PRIVATE; @@ -53,6 +54,7 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; +import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; @@ -191,6 +193,7 @@ import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; +import android.app.NotificationManager.Policy; import android.app.PendingIntent; import android.app.Person; import android.app.RemoteInput; @@ -468,6 +471,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationChannel mSilentChannel = new NotificationChannel("low", "low", IMPORTANCE_LOW); + NotificationChannel mMinChannel = new NotificationChannel("min", "min", IMPORTANCE_MIN); + private static final int NOTIFICATION_LOCATION_UNKNOWN = 0; private static final String VALID_CONVO_SHORTCUT_ID = "shortcut"; @@ -558,8 +563,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Parameters(name = "{0}") public static List getParams() { - return FlagsParameterization.allCombinationsOf( - FLAG_ALL_NOTIFS_NEED_TTL); + return FlagsParameterization.allCombinationsOf(); } public NotificationManagerServiceTest(FlagsParameterization flags) { @@ -652,7 +656,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt())) .thenReturn(INVALID_TASK_ID); mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class)); - when(mUm.getProfileIds(eq(mUserId), eq(false))).thenReturn(new int[] { mUserId }); + when(mUm.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mUmInternal.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId}); when(mAmi.getCurrentUserId()).thenReturn(mUserId); when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true); @@ -856,15 +861,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mInternalService = mService.getInternalService(); mBinderService.createNotificationChannels(mPkg, new ParceledListSlice( - Arrays.asList(mTestNotificationChannel, mSilentChannel))); + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); mBinderService.createNotificationChannels(PKG_P, new ParceledListSlice( - Arrays.asList(mTestNotificationChannel, mSilentChannel))); + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice( - Arrays.asList(mTestNotificationChannel, mSilentChannel))); + Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel))); assertNotNull(mBinderService.getNotificationChannel( mPkg, mContext.getUserId(), mPkg, TEST_CHANNEL_ID)); assertNotNull(mBinderService.getNotificationChannel( mPkg, mContext.getUserId(), mPkg, mSilentChannel.getId())); + assertNotNull(mBinderService.getNotificationChannel( + mPkg, mContext.getUserId(), mPkg, mMinChannel.getId())); clearInvocations(mRankingHandler); when(mPermissionHelper.hasPermission(mUid)).thenReturn(true); @@ -943,6 +950,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } } + private ShortcutInfo createMockConvoShortcut() { + ShortcutInfo info = mock(ShortcutInfo.class); + when(info.getPackage()).thenReturn(mPkg); + when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID); + when(info.getUserId()).thenReturn(USER_SYSTEM); + when(info.isLongLived()).thenReturn(true); + when(info.isEnabled()).thenReturn(true); + return info; + } + private void simulatePackageSuspendBroadcast(boolean suspend, String pkg, int uid) { // mimics receive broadcast that package is (un)suspended @@ -4637,7 +4654,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { doThrow(new SecurityException("no access")).when(mUgmInternal) .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri), - anyInt(), eq(Process.myUserHandle().getIdentifier())); + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + mBinderService.updateNotificationChannelFromPrivilegedListener( + null, mPkg, Process.myUserHandle(), updatedNotificationChannel); + + verify(mPreferencesHelper, times(1)).updateNotificationChannel( + anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean()); + + verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg), + eq(Process.myUserHandle()), eq(mTestNotificationChannel), + eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED)); + } + + @Test + public void + testUpdateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm() + throws Exception { + mService.setPreferencesHelper(mPreferencesHelper); + when(mCompanionMgr.getAssociations(mPkg, mUserId)) + .thenReturn(singletonList(mock(AssociationInfo.class))); + when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(), + eq(mTestNotificationChannel.getId()), anyBoolean())) + .thenReturn(mTestNotificationChannel); + + // Missing Uri permissions for the old channel sound + final Uri oldSoundUri = Settings.System.DEFAULT_NOTIFICATION_URI; + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(Process.myUid()), any(), eq(oldSoundUri), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + // Has Uri permissions for the old channel sound + final Uri newSoundUri = Uri.parse("content://media/test/sound/uri"); + final NotificationChannel updatedNotificationChannel = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT); + updatedNotificationChannel.setSound(newSoundUri, + updatedNotificationChannel.getAudioAttributes()); mBinderService.updateNotificationChannelFromPrivilegedListener( null, mPkg, Process.myUserHandle(), updatedNotificationChannel); @@ -15921,6 +15973,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(updatedRule.getValue().isEnabled()).isFalse(); } + @Test + @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI}) + public void setNotificationPolicy_fromSystemApp_appliesPriorityChannelsAllowed() + throws Exception { + setUpRealZenTest(); + // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default"). + mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0, + Policy.policyState(true, true), 0), + ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID); + + // The caller will supply states with "wrong" hasPriorityChannels. + int stateBlockingPriorityChannels = Policy.policyState(false, false); + mBinderService.setNotificationPolicy(mPkg, + new Policy(1, 0, 0, 0, stateBlockingPriorityChannels, 0), false); + + // hasPriorityChannels is untouched and allowPriorityChannels was updated. + assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1); + assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo( + Policy.policyState(true, false)); + + // Same but setting allowPriorityChannels to true. + int stateAllowingPriorityChannels = Policy.policyState(false, true); + mBinderService.setNotificationPolicy(mPkg, + new Policy(2, 0, 0, 0, stateAllowingPriorityChannels, 0), false); + + assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(2); + assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo( + Policy.policyState(true, true)); + } + + @Test + @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI}) + @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES) + public void setNotificationPolicy_fromRegularAppThatCanModifyPolicy_ignoresState() + throws Exception { + setUpRealZenTest(); + // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default"). + mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0, + Policy.policyState(true, true), 0), + ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID); + mService.setCallerIsNormalPackage(); + + mBinderService.setNotificationPolicy(mPkg, + new Policy(1, 0, 0, 0, Policy.policyState(false, false), 0), false); + + // Policy was updated but the attempt to change state was ignored (it's a @hide API). + assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1); + assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo( + Policy.policyState(true, true)); + } + /** Prepares for a zen-related test that uses the real {@link ZenModeHelper}. */ private void setUpRealZenTest() throws Exception { when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) @@ -16540,13 +16643,297 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); } - private ShortcutInfo createMockConvoShortcut() { - ShortcutInfo info = mock(ShortcutInfo.class); - when(info.getPackage()).thenReturn(mPkg); - when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID); - when(info.getUserId()).thenReturn(USER_SYSTEM); - when(info.isLongLived()).thenReturn(true); - when(info.isEnabled()).thenReturn(true); - return info; + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testSetCanBePromoted_granted() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // qualifying enqueued notification + Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0, + n1, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel); + + // another package but otherwise would qualify + Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0, + n2, UserHandle.getUserHandleForUid(UID_O), null, 0); + NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel); + + // not-qualifying posted notification + Notification n3 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + + StatusBarNotification sbn3 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n3, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r3 = new NotificationRecord(mContext, sbn3, mTestNotificationChannel); + + mService.addNotification(r3); + mService.addNotification(r2); + mService.addNotification(r); + mService.addEnqueuedNotification(r1); + + mBinderService.setCanBePromoted(mPkg, mUid, true, true); + + waitForIdle(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + // the posted one + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isTrue(); + // the enqueued one + assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isTrue(); + // the other app + assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + // same app, not qualifying + assertThat(mService.hasFlag(r3.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addNotification(r); + + mBinderService.setCanBePromoted(mPkg, mUid, true, true); + waitForIdle(); + mBinderService.setCanBePromoted(mPkg, mUid, true, true); + waitForIdle(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testSetCanBePromoted_revoked() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // start from true state + mBinderService.setCanBePromoted(mPkg, mUid, true, true); + + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + // qualifying enqueued notification + Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0, + n1, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel); + + // doesn't qualify, same package + Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .build(); + StatusBarNotification sbn2 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n2, UserHandle.getUserHandleForUid(UID_O), null, 0); + NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel); + + mService.addNotification(r2); + mService.addNotification(r); + mService.addEnqueuedNotification(r1); + + mBinderService.setCanBePromoted(mPkg, mUid, false, true); + + waitForIdle(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + // the posted one + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isFalse(); + // the enqueued one + assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + // the not qualifying one + assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + // start from true state + mBinderService.setCanBePromoted(mPkg, mUid, true, true); + + // qualifying posted notification + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post + .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addNotification(r); + + mBinderService.setCanBePromoted(mPkg, mUid, false, true); + waitForIdle(); + mBinderService.setCanBePromoted(mPkg, mUid, false, true); + waitForIdle(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testPostPromotableNotification() throws Exception { + mBinderService.setCanBePromoted(mPkg, mUid, true, true); + assertThat(mBinderService.appCanBePromoted(mPkg, mUid)).isTrue(); + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isTrue(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testPostPromotableNotification_noPermission() throws Exception { + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testPostPromotableNotification_unimportantNotification() throws Exception { + mBinderService.setCanBePromoted(mPkg, mUid, true, true); + mContext.getTestablePermissions().setPermission( + android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED); + Notification n = new Notification.Builder(mContext, mMinChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG")) + .setColor(Color.WHITE) + .setColorized(true) + .build(); + + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + sbn.getId(), sbn.getNotification(), sbn.getUserId()); + waitForIdle(); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(NotificationRecord.class); + verify(mListeners, times(1)).prepareNotifyPostedLocked( + captor.capture(), any(), anyBoolean()); + + assertThat(mService.hasFlag(captor.getValue().getNotification().flags, + FLAG_PROMOTED_ONGOING)).isFalse(); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 1905ae4aec4bebad8385d335d3808e81e3358ef5..d64b9e858c647104cab9c9d08e7d2e1844fbfb19 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -45,11 +45,13 @@ import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE; +import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE; +import static android.content.ContentResolver.SCHEME_CONTENT; +import static android.content.ContentResolver.SCHEME_FILE; import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; - import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION; import static android.service.notification.Flags.notificationClassification; @@ -59,9 +61,11 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; +import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI; import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA; import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; +import static com.android.server.notification.PreferencesHelper.LockableAppFields.USER_LOCKED_PROMOTABLE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_GROUP_COUNT_LIMIT; import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; @@ -83,6 +87,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -368,10 +373,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - false, mClock); + mUgmInternal, false, mClock); mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - false, mClock); + mUgmInternal, false, mClock); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() @@ -518,6 +523,17 @@ public class PreferencesHelperTest extends UiServiceTestCase { doneLatch.await(); } + private static NotificationChannel cloneChannel(NotificationChannel original) { + Parcel parcel = Parcel.obtain(); + try { + original.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return NotificationChannel.CREATOR.createFromParcel(parcel); + } finally { + parcel.recycle(); + } + } + @Test public void testWriteXml_onlyBackupsTargetUser() throws Exception { // Setup package notifications. @@ -631,6 +647,9 @@ public class PreferencesHelperTest extends UiServiceTestCase { } mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true); + if (android.app.Flags.uiRichOngoing()) { + mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true, true); + } ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false, UserHandle.USER_ALL, channel1.getId(), channel2.getId(), @@ -641,6 +660,11 @@ public class PreferencesHelperTest extends UiServiceTestCase { loadStreamXml(baos, false, UserHandle.USER_ALL); assertTrue(mXmlHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); + if (android.app.Flags.uiRichOngoing()) { + assertThat(mXmlHelper.canBePromoted(PKG_N_MR1, UID_N_MR1)).isTrue(); + assertThat(mXmlHelper.getAppLockedFields(PKG_N_MR1, UID_N_MR1) & USER_LOCKED_PROMOTABLE) + .isNotEqualTo(0); + } assertEquals(channel1, mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false)); compareChannels(channel2, @@ -763,7 +787,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_oldXml_migrates() throws Exception { mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - /* showReviewPermissionsNotification= */ true, mClock); + mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock); String xml = "\n" + "\n" + "\n" @@ -958,7 +982,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_permissionNotificationOff() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - /* showReviewPermissionsNotification= */ false, mClock); + mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock); String xml = "\n" + "\n" @@ -1017,7 +1041,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - /* showReviewPermissionsNotification= */ true, mClock); + mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock); String xml = "\n" + "\n" @@ -1689,7 +1713,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // simulate load after reboot mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - false, mClock); + mUgmInternal, false, mClock); loadByteArrayXml(baos.toByteArray(), false, USER_ALL); // Trigger 2nd restore pass @@ -1744,7 +1768,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // simulate load after reboot mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - false, mClock); + mUgmInternal, false, mClock); loadByteArrayXml(xml.getBytes(), false, USER_ALL); // Trigger 2nd restore pass @@ -1822,10 +1846,10 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - false, mClock); + mUgmInternal, false, mClock); mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles, - false, mClock); + mUgmInternal, false, mClock); NotificationChannel channel = new NotificationChannel("id", "name", IMPORTANCE_LOW); @@ -3028,6 +3052,64 @@ public class PreferencesHelperTest extends UiServiceTestCase { PKG_N_MR1, UID_N_MR1, channel.getId(), false).getSound()); } + @Test + @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI) + public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() { + final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri"); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + final NotificationChannel channel = new NotificationChannel("id2", "name2", + NotificationManager.IMPORTANCE_DEFAULT); + channel.setSound(sound, mAudioAttributes); + + assertThrows(SecurityException.class, + () -> mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, + true, false, UID_N_MR1, false)); + assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)) + .isNull(); + } + + @Test + @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI) + public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() { + final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound"); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(UID_N_MR1), any(), any(), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + final NotificationChannel channel = new NotificationChannel("id2", "name2", + NotificationManager.IMPORTANCE_DEFAULT); + channel.setSound(sound, mAudioAttributes); + + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1, + false); + assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true) + .getSound()).isEqualTo(sound); + } + + @Test + @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI) + public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() { + final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound"); + + doThrow(new SecurityException("no access")).when(mUgmInternal) + .checkGrantUriPermission(eq(UID_N_MR1), any(), any(), + anyInt(), eq(Process.myUserHandle().getIdentifier())); + + final NotificationChannel channel = new NotificationChannel("id2", "name2", + NotificationManager.IMPORTANCE_DEFAULT); + channel.setSound(sound, mAudioAttributes); + + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1, + false); + assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true) + .getSound()).isEqualTo(sound); + } + @Test public void testPermanentlyDeleteChannels() throws Exception { NotificationChannel channel1 = @@ -6293,14 +6375,31 @@ public class PreferencesHelperTest extends UiServiceTestCase { }, 20, 50); } - private static NotificationChannel cloneChannel(NotificationChannel original) { - Parcel parcel = Parcel.obtain(); - try { - original.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - return NotificationChannel.CREATOR.createFromParcel(parcel); - } finally { - parcel.recycle(); - } + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testNoAppHasPermissionToPromoteByDefault() { + mHelper.setShowBadge(PKG_P, UID_P, true); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testSetCanBePromoted() { + mHelper.setCanBePromoted(PKG_P, UID_P, true, true); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue(); + + mHelper.setCanBePromoted(PKG_P, UID_P, false, true); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse(); + verify(mHandler, never()).requestSort(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING) + public void testSetCanBePromoted_allowlistNotOverrideUser() { + mHelper.setCanBePromoted(PKG_P, UID_P, true, true); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue(); + + mHelper.setCanBePromoted(PKG_P, UID_P, false, false); + assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue(); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 12069284e46202a64e4d6bcdce5452e9779137dc..d4cba8d726fbb69853638d79f9ec467a15289b06 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -18,7 +18,7 @@ package com.android.server.notification; import static android.app.AutomaticZenRule.TYPE_BEDTIME; import static android.app.AutomaticZenRule.TYPE_IMMERSIVE; -import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR; +import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME; import static android.app.AutomaticZenRule.TYPE_UNKNOWN; import static android.app.Flags.FLAG_MODES_API; import static android.app.Flags.FLAG_MODES_UI; @@ -221,7 +221,7 @@ import platform.test.runner.parameterized.Parameters; @TestableLooper.RunWithLooper public class ZenModeHelperTest extends UiServiceTestCase { - private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; + private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; private static final String SCHEDULE_DEFAULT_RULE_ID = ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID; private static final String CUSTOM_PKG_NAME = "not.android"; @@ -1216,7 +1216,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // list for tracking which ids we've seen in the pulled atom output List ids = new ArrayList<>(); - ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS); + ids.addAll(ZenModeConfig.getDefaultRuleIds()); ids.add(""); // empty string for root config for (StatsEvent ev : events) { @@ -1793,14 +1793,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap rules = mZenModeHelper.mConfig.automaticRules; assertTrue(rules.size() != 0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertTrue(rules.containsKey(defaultId)); } assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy()); } - @Test public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception { setupZenConfig(); @@ -1830,7 +1829,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap rules = mZenModeHelper.mConfig.automaticRules; assertTrue(rules.size() != 0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertTrue(rules.containsKey(defaultId)); } assertFalse(rules.containsKey("customRule")); @@ -1839,6 +1838,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @DisableFlags(FLAG_MODES_UI) // modes_ui has only 1 default rule public void testReadXmlOnlyOneDefaultRuleExists() throws Exception { setupZenConfig(); Policy originalPolicy = mZenModeHelper.getNotificationPolicy(); @@ -1882,11 +1882,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap rules = mZenModeHelper.mConfig.automaticRules; - assertTrue(rules.size() != 0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { - assertTrue(rules.containsKey(defaultId)); + assertThat(rules).isNotEmpty(); + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { + assertThat(rules).containsKey(defaultId); } - assertFalse(rules.containsKey("customRule")); + assertThat(rules).doesNotContainKey("customRule"); assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy()); } @@ -1932,13 +1932,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId( defaultEventRuleInfo); - defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; + defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; defaultScheduleRule.zenPolicy = new ZenPolicy.Builder() .allowAlarms(false) .allowMedia(false) .allowRepeatCallers(false) .build(); - automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule); + automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule); mZenModeHelper.mConfig.automaticRules = automaticRules; @@ -1951,18 +1951,19 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); // check default rules + int expectedNumAutoRules = 1 + ZenModeConfig.getDefaultRuleIds().size(); // custom + default ArrayMap rules = mZenModeHelper.mConfig.automaticRules; - assertEquals(3, rules.size()); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { - assertTrue(rules.containsKey(defaultId)); + assertThat(rules).hasSize(expectedNumAutoRules); + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { + assertThat(rules).containsKey(defaultId); } - assertTrue(rules.containsKey("customRule")); + assertThat(rules).containsKey("customRule"); assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy()); List events = new LinkedList<>(); mZenModeHelper.pullRules(events); - assertEquals(4, events.size()); + assertThat(events).hasSize(expectedNumAutoRules + 1); // auto + manual } @Test @@ -2151,8 +2152,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId( defaultEventRuleInfo); - defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; - automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule); + defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; + automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule); mZenModeHelper.mConfig.automaticRules = automaticRules; @@ -2167,7 +2168,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // check default rules ArrayMap rules = mZenModeHelper.mConfig.automaticRules; assertThat(rules.size()).isGreaterThan(0); - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertThat(rules).containsKey(defaultId); ZenRule rule = rules.get(defaultId); assertThat(rule.zenPolicy).isNotNull(); @@ -2371,7 +2372,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Find default rules; check they have non-null policies; check that they match the default // and not whatever has been set up in setupZenConfig. ArrayMap rules = mZenModeHelper.mConfig.automaticRules; - for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + for (String defaultId : ZenModeConfig.getDefaultRuleIds()) { assertThat(rules).containsKey(defaultId); ZenRule rule = rules.get(defaultId); assertThat(rule.zenPolicy).isNotNull(); @@ -6884,7 +6885,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.onUserSwitched(101); ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get( - ZenModeConfig.EVENTS_DEFAULT_RULE_ID); + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); assertThat(eventsRule).isNotNull(); assertThat(eventsRule.zenPolicy).isNull(); @@ -6900,7 +6901,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.onUserSwitched(201); ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get( - ZenModeConfig.EVENTS_DEFAULT_RULE_ID); + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); assertThat(eventsRule).isNotNull(); assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy()); @@ -6915,11 +6916,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.onUserSwitched(301); ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get( - ZenModeConfig.EVENTS_DEFAULT_RULE_ID); + ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); assertThat(eventsRule).isNotNull(); assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy()); - assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_CALENDAR); + assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_TIME); assertThat(eventsRule.triggerDescription).isNotEmpty(); } @@ -7008,6 +7009,46 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(zenRule.zenPolicy).isNotSameInstanceAs(mZenModeHelper.getDefaultZenPolicy()); } + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void readXml_withDisabledEventsRule_deletesIt() throws Exception { + ZenRule rule = new ZenRule(); + rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; + rule.name = "Events"; + rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + rule.conditionId = Uri.parse("events"); + + rule.enabled = false; + mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule); + ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI); + TypedXmlPullParser parser = getParserForByteStream(xmlBytes); + + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertThat(mZenModeHelper.mConfig.automaticRules).doesNotContainKey( + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); + } + + @Test + @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI}) + public void readXml_withEnabledEventsRule_keepsIt() throws Exception { + ZenRule rule = new ZenRule(); + rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID; + rule.name = "Events"; + rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + rule.conditionId = Uri.parse("events"); + + rule.enabled = true; + mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule); + ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI); + TypedXmlPullParser parser = getParserForByteStream(xmlBytes); + + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertThat(mZenModeHelper.mConfig.automaticRules).containsKey( + ZenModeConfig.EVENTS_OBSOLETE_RULE_ID); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java index c7a136ad0f9f3ad947a7d119bf899704f24cc9c3..23ee893e309a979464aa6ee4584a2ef3f0015b62 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java @@ -44,7 +44,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -52,12 +53,14 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.IActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManagerInternal; import android.media.AudioManager; import android.os.Handler; @@ -79,12 +82,10 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; -import com.android.server.LocalServices; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.vibrator.VibrationSession.CallerInfo; import com.android.server.vibrator.VibrationSession.Status; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -101,8 +102,7 @@ public class VibrationSettingsTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private static final int OLD_USER_ID = 123; - private static final int NEW_USER_ID = 456; + private static final int USER_ID = 123; private static final int UID = 1; private static final int VIRTUAL_DEVICE_ID = 1; private static final String SYSUI_PACKAGE_NAME = "sysui"; @@ -132,13 +132,12 @@ public class VibrationSettingsTest { @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock; @Mock private PackageManagerInternal mPackageManagerInternalMock; @Mock private AudioManager mAudioManagerMock; + @Mock private IActivityManager mActivityManagerMock; @Mock private VibrationConfig mVibrationConfigMock; private TestLooper mTestLooper; private ContextWrapper mContextSpy; private VibrationSettings mVibrationSettings; - private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; - private BroadcastReceiver mRegisteredBatteryBroadcastReceiver; @Before public void setUp() throws Exception { @@ -146,24 +145,21 @@ public class VibrationSettingsTest { mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); - when(mContextSpy.getContentResolver()).thenReturn(contentResolver); - when(mContextSpy.getSystemService(eq(Context.AUDIO_SERVICE))).thenReturn(mAudioManagerMock); - doAnswer(invocation -> { - mRegisteredPowerModeListener = invocation.getArgument(0); - return null; - }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any()); + doReturn(contentResolver).when(mContextSpy).getContentResolver(); + + // Make sure broadcast receivers are not registered for this test, to avoid flakes. + doReturn(null).when(mContextSpy) + .registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class), anyInt()); + when(mPackageManagerInternalMock.getSystemUiServiceComponent()) .thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, "")); - removeServicesForTest(); - addServicesForTest(); - setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM); setIgnoreVibrationsOnWirelessCharger(false); - createSystemReadyVibrationSettings(); - mockGoToSleep(/* goToSleepTime= */ 0, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT); + + createSystemReadyVibrationSettings(); } private void createSystemReadyVibrationSettings() { @@ -177,37 +173,18 @@ public class VibrationSettingsTest { setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); setRingerMode(AudioManager.RINGER_MODE_NORMAL); - mVibrationSettings.onSystemReady(); - } - - private void removeServicesForTest() { - LocalServices.removeServiceForTest(PowerManagerInternal.class); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); - } - - private void addServicesForTest() { - LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock); - LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock); - LocalServices.addService(VirtualDeviceManagerInternal.class, - mVirtualDeviceManagerInternalMock); - } - - @After - public void tearDown() throws Exception { - removeServicesForTest(); + mVibrationSettings.onSystemReady(mPackageManagerInternalMock, mPowerManagerInternalMock, + mActivityManagerMock, mVirtualDeviceManagerInternalMock, mAudioManagerMock); } @Test public void create_withOnlyRequiredSystemServices() { - // The only core services that we depend on are PowerManager and PackageManager - removeServicesForTest(); - LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock); - LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock); - VibrationSettings minimalVibrationSettings = new VibrationSettings(mContextSpy, new Handler(mTestLooper.getLooper()), mVibrationConfigMock); - minimalVibrationSettings.onSystemReady(); + + // The only core services that we depend on are Power, Package and Activity managers + minimalVibrationSettings.onSystemReady(mPackageManagerInternalMock, + mPowerManagerInternalMock, mActivityManagerMock, null, null); } @Test @@ -215,8 +192,8 @@ public class VibrationSettingsTest { mVibrationSettings.addListener(mListenerMock); // Testing the broadcast flow manually. - mVibrationSettings.mUserSwitchObserver.onUserSwitching(NEW_USER_ID); - mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID); + mVibrationSettings.mUserSwitchObserver.onUserSwitching(USER_ID); + mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(USER_ID); verify(mListenerMock, times(2)).onChange(); } @@ -226,9 +203,9 @@ public class VibrationSettingsTest { mVibrationSettings.addListener(mListenerMock); // Testing the broadcast flow manually. - mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + mVibrationSettings.mRingerModeBroadcastReceiver.onReceive(mContextSpy, new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); - mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + mVibrationSettings.mRingerModeBroadcastReceiver.onReceive(mContextSpy, new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); verify(mListenerMock, times(2)).onChange(); @@ -250,9 +227,9 @@ public class VibrationSettingsTest { mVibrationSettings.addListener(mListenerMock); // Testing the broadcast flow manually. - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); // No change. + mVibrationSettings.mLowPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); + mVibrationSettings.mLowPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); + mVibrationSettings.mLowPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); // Noop. verify(mListenerMock, times(2)).onChange(); } @@ -268,9 +245,9 @@ public class VibrationSettingsTest { // Trigger multiple observers manually. mVibrationSettings.mSettingObserver.onChange(false); - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID); - mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + mVibrationSettings.mLowPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); + mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(USER_ID); + mVibrationSettings.mRingerModeBroadcastReceiver.onReceive(mContextSpy, new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); verifyNoMoreInteractions(mListenerMock); @@ -311,11 +288,12 @@ public class VibrationSettingsTest { @Test public void wirelessChargingVibrationsEnabled_doesNotRegisterBatteryReceiver_allowsAnyUsage() { - setBatteryReceiverRegistrationResult(getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS)); setIgnoreVibrationsOnWirelessCharger(false); createSystemReadyVibrationSettings(); - assertNull(mRegisteredBatteryBroadcastReceiver); + verify(mContextSpy, never()).registerReceiver(any(BroadcastReceiver.class), + argThat(filter -> filter.matchAction(Intent.ACTION_BATTERY_CHANGED)), anyInt()); + for (int usage : ALL_USAGES) { assertVibrationNotIgnoredForUsage(usage); } @@ -323,7 +301,6 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_noBatteryIntentWhenSystemReady_allowsAnyUsage() { - setBatteryReceiverRegistrationResult(null); setIgnoreVibrationsOnWirelessCharger(true); createSystemReadyVibrationSettings(); @@ -335,7 +312,10 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_onNonWirelessChargerWhenSystemReady_allowsAnyUsage() { Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB); - setBatteryReceiverRegistrationResult(nonWirelessChargingIntent); + doReturn(nonWirelessChargingIntent).when(mContextSpy).registerReceiver( + any(BroadcastReceiver.class), + argThat(filter -> filter.matchAction(Intent.ACTION_BATTERY_CHANGED)), anyInt()); + setIgnoreVibrationsOnWirelessCharger(true); createSystemReadyVibrationSettings(); @@ -347,7 +327,10 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_onWirelessChargerWhenSystemReady_doesNotAllowFromAnyUsage() { Intent wirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS); - setBatteryReceiverRegistrationResult(wirelessChargingIntent); + doReturn(wirelessChargingIntent).when(mContextSpy).registerReceiver( + any(BroadcastReceiver.class), + argThat(filter -> filter.matchAction(Intent.ACTION_BATTERY_CHANGED)), anyInt()); + setIgnoreVibrationsOnWirelessCharger(true); createSystemReadyVibrationSettings(); @@ -358,13 +341,12 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_receivesWirelessChargingIntent_doesNotAllowFromAnyUsage() { - Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB); - setBatteryReceiverRegistrationResult(nonWirelessChargingIntent); setIgnoreVibrationsOnWirelessCharger(true); createSystemReadyVibrationSettings(); Intent wirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS); - mRegisteredBatteryBroadcastReceiver.onReceive(mContextSpy, wirelessChargingIntent); + mVibrationSettings.mBatteryBroadcastReceiver.onReceive( + mContextSpy, wirelessChargingIntent); for (int usage : ALL_USAGES) { assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER); @@ -373,17 +355,21 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_receivesNonWirelessChargingIntent_allowsAnyUsage() { - Intent wirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS); - setBatteryReceiverRegistrationResult(wirelessChargingIntent); setIgnoreVibrationsOnWirelessCharger(true); createSystemReadyVibrationSettings(); + + Intent wirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_WIRELESS); + mVibrationSettings.mBatteryBroadcastReceiver.onReceive( + mContextSpy, wirelessChargingIntent); + // Check that initially, all usages are ignored due to the wireless charging. for (int usage : ALL_USAGES) { assertVibrationIgnoredForUsage(usage, Status.IGNORED_ON_WIRELESS_CHARGER); } Intent nonWirelessChargingIntent = getBatteryChangedIntent(BATTERY_PLUGGED_USB); - mRegisteredBatteryBroadcastReceiver.onReceive(mContextSpy, nonWirelessChargingIntent); + mVibrationSettings.mBatteryBroadcastReceiver.onReceive( + mContextSpy, nonWirelessChargingIntent); for (int usage : ALL_USAGES) { assertVibrationNotIgnoredForUsage(usage); @@ -400,7 +386,7 @@ public class VibrationSettingsTest { USAGE_HARDWARE_FEEDBACK )); - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); + mVibrationSettings.mLowPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); for (int usage : ALL_USAGES) { if (expectedAllowedVibrations.contains(usage)) { @@ -413,7 +399,7 @@ public class VibrationSettingsTest { @Test public void shouldIgnoreVibration_notInBatterySaverMode_allowsAnyUsage() { - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); + mVibrationSettings.mLowPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); for (int usage : ALL_USAGES) { assertVibrationNotIgnoredForUsage(usage); @@ -596,7 +582,7 @@ public class VibrationSettingsTest { // Testing the broadcast flow manually. when(mAudioManagerMock.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT); - mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + mVibrationSettings.mRingerModeBroadcastReceiver.onReceive(mContextSpy, new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); assertVibrationIgnoredForUsage(USAGE_RINGTONE, Status.IGNORED_FOR_RINGER_MODE); @@ -852,16 +838,15 @@ public class VibrationSettingsTest { mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); // Test early update of settings based on new user id. - putUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW, - NEW_USER_ID); - mVibrationSettings.mUserSwitchObserver.onUserSwitching(NEW_USER_ID); + putUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW, USER_ID); + mVibrationSettings.mUserSwitchObserver.onUserSwitching(USER_ID); assertEquals(VIBRATION_INTENSITY_LOW, mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); // Test later update of settings for UserHandle.USER_CURRENT. putUserSetting(Settings.System.RING_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW, UserHandle.USER_CURRENT); - mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(NEW_USER_ID); + mVibrationSettings.mUserSwitchObserver.onUserSwitchComplete(USER_ID); assertEquals(VIBRATION_INTENSITY_LOW, mVibrationSettings.getCurrentIntensity(USAGE_RINGTONE)); } @@ -1009,7 +994,7 @@ public class VibrationSettingsTest { private void setRingerMode(int ringerMode) { when(mAudioManagerMock.getRingerModeInternal()).thenReturn(ringerMode); // Mock AudioManager broadcast of internal ringer mode change. - mVibrationSettings.mSettingChangeReceiver.onReceive(mContextSpy, + mVibrationSettings.mRingerModeBroadcastReceiver.onReceive(mContextSpy, new Intent(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); } @@ -1024,14 +1009,6 @@ public class VibrationSettingsTest { return new CallerInfo(attrs, uid, VIRTUAL_DEVICE_ID, opPkg, null); } - private void setBatteryReceiverRegistrationResult(Intent result) { - doAnswer(invocation -> { - mRegisteredBatteryBroadcastReceiver = invocation.getArgument(0); - return result; - }).when(mContextSpy).registerReceiver(any(BroadcastReceiver.class), - argThat(filter -> filter.matchAction(Intent.ACTION_BATTERY_CHANGED)), anyInt()); - } - private Intent getBatteryChangedIntent(int extraPluggedValue) { Intent batteryIntent = new Intent(Intent.ACTION_BATTERY_CHANGED); batteryIntent.putExtra(EXTRA_PLUGGED, extraPluggedValue); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index 31cc50f1829907622091416e4a3b19bf10f76686..e83a4b22d9bf56eeb09b7bcae1ffb4d7478cc438 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -29,13 +29,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -96,8 +94,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.stream.Collectors; @@ -187,11 +183,11 @@ public class VibrationThreadTest { mVibratorProviders.clear(); CombinedVibration effect = CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED); + verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); } @Test @@ -200,11 +196,11 @@ public class VibrationThreadTest { .addNext(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .addNext(3, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED); + verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); } @Test @@ -212,34 +208,34 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibrationEffect effect = VibrationEffect.createOneShot(10, 100); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test public void vibrate_oneShotWithoutAmplitudeControl_runsVibrationWithDefaultAmplitude() { VibrationEffect effect = VibrationEffect.createOneShot(10, 100); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty()); } @@ -249,17 +245,17 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.createWaveform( new long[]{5, 5, 5}, new int[]{1, 2, 3}, -1); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(15L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(15)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @@ -277,13 +273,13 @@ public class VibrationThreadTest { mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f); CompletableFuture mRequestVibrationParamsFuture = CompletableFuture.completedFuture( null); - long vibrationId = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture, + HalVibration vibration = startThreadAndDispatcher(effect, mRequestVibrationParamsFuture, USAGE_RINGTONE); waitForCompletion(); verify(mStatsLoggerMock, never()).logVibrationParamRequestTimeout(UID); assertEquals(Arrays.asList(expectedOneShot(15)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); List amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes(); for (int i = 0; i < amplitudes.size(); i++) { assertTrue(amplitudes.get(i) < 1 / 255f); @@ -301,12 +297,13 @@ public class VibrationThreadTest { new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1); CompletableFuture neverCompletingFuture = new CompletableFuture<>(); - long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE); + HalVibration vibration = startThreadAndDispatcher(effect, neverCompletingFuture, + USAGE_RINGTONE); waitForCompletion(); verify(mStatsLoggerMock).logVibrationParamRequestTimeout(UID); assertEquals(Arrays.asList(expectedOneShot(15)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(1, 1, 1), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @@ -319,13 +316,13 @@ public class VibrationThreadTest { int[] amplitudes = new int[]{1, 2, 3}; VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5, 5, 5}, amplitudes, 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue( waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2 * amplitudes.length, TEST_TIMEOUT_MILLIS)); // Vibration still running after 2 cycles. - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); assertTrue(mControllers.get(VIBRATOR_ID).isVibrating()); Vibration.EndInfo cancelVibrationInfo = new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED, @@ -333,15 +330,15 @@ public class VibrationThreadTest { /* uid= */ 1, /* deviceId= */ -1, /* opPkg= */ null, /* reason= */ null)); mVibrationConductor.notifyCancelled(cancelVibrationInfo, /* immediate= */ false); waitForCompletion(); - assertFalse(mThread.isRunningVibrationId(vibrationId)); + assertFalse(mThread.isRunningVibrationId(vibration.id)); verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong()); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verifyCallbacksTriggered(vibrationId, cancelVibrationInfo); + verifyCallbacksTriggered(vibration, Status.CANCELLED_SUPERSEDED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); List playedAmplitudes = fakeVibrator.getAmplitudes(); - assertFalse(fakeVibrator.getEffectSegments(vibrationId).isEmpty()); + assertFalse(fakeVibrator.getEffectSegments(vibration.id).isEmpty()); assertFalse(playedAmplitudes.isEmpty()); for (int i = 0; i < playedAmplitudes.size(); i++) { @@ -358,17 +355,17 @@ public class VibrationThreadTest { int[] amplitudes = new int[]{1, 2, 3}; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{1, 10, 100}, amplitudes, 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(5000)), - fakeVibrator.getEffectSegments(vibrationId)); + fakeVibrator.getEffectSegments(vibration.id)); } @Test @@ -377,16 +374,16 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[]{0, 100, 50, 100, 0, 0, 0, 50}, /* repeat= */ -1); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(300L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); - assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)) + assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)) .isEqualTo(expectedOneShots(100L, 150L)); } @@ -398,16 +395,16 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[]{0, 100, 0, 50, 50, 0, 100, 50}, amplitudes, /* repeat= */ -1); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(350L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); - assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)) + assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)) .isEqualTo(expectedOneShots(200L, 50L)); } @@ -420,7 +417,7 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[]{0, 200, 50, 100, 0, 50, 50, 100}, /* repeat= */ 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); // We are expect this test to repeat the vibration effect twice, which would result in 5 // segments being played: // 200ms ON @@ -428,16 +425,17 @@ public class VibrationThreadTest { // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms) // 150ms ON (100ms + 50ms, skips 0ms) // 300ms ON (100ms + 200ms looping to the start and skipping first 0ms) - assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() >= 5, + assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() >= 5, 5000L + TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER); assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); - assertThat(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).subList(0, 5)) + assertThat( + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).subList(0, 5)) .isEqualTo(expectedOneShots(200L, 150L, 300L, 150L, 300L)); } @@ -458,18 +456,18 @@ public class VibrationThreadTest { VibrationEffect repeatingEffect = VibrationEffect.startComposition() .repeatEffectIndefinitely(effect) .compose(); - long vibrationId = startThreadAndDispatcher(repeatingEffect); + HalVibration vibration = startThreadAndDispatcher(repeatingEffect); - assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(), + assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(), TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false); waitForCompletion(); // PWLE size max was used to generate a single vibrate call with 10 segments. - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); - assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size()); + assertEquals(10, fakeVibrator.getEffectSegments(vibration.id).size()); } @Test @@ -487,18 +485,18 @@ public class VibrationThreadTest { VibrationEffect repeatingEffect = VibrationEffect.startComposition() .repeatEffectIndefinitely(effect) .compose(); - long vibrationId = startThreadAndDispatcher(repeatingEffect); + HalVibration vibration = startThreadAndDispatcher(repeatingEffect); - assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(), + assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(), TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false); waitForCompletion(); // Composition size max was used to generate a single vibrate call with 10 primitives. - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); - assertEquals(10, fakeVibrator.getEffectSegments(vibrationId).size()); + assertEquals(10, fakeVibrator.getEffectSegments(vibration.id).size()); } @Test @@ -510,17 +508,17 @@ public class VibrationThreadTest { int[] amplitudes = new int[]{1, 2, 3}; VibrationEffect effect = VibrationEffect.createWaveform( new long[]{5000, 500, 50}, amplitudes, 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> !fakeVibrator.getAmplitudes().isEmpty(), TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(5550)), - fakeVibrator.getEffectSegments(vibrationId)); + fakeVibrator.getEffectSegments(vibration.id)); } @LargeTest @@ -534,17 +532,17 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.createWaveform( /* timings= */ new long[]{expectedOnDuration - 100, 50}, /* amplitudes= */ new int[]{1, 2}, /* repeat= */ 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); - assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibrationId).size() > 1, + assertTrue(waitUntil(() -> fakeVibrator.getEffectSegments(vibration.id).size() > 1, expectedOnDuration + TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); - List effectSegments = fakeVibrator.getEffectSegments(vibrationId); + List effectSegments = fakeVibrator.getEffectSegments(vibration.id); // First time, turn vibrator ON for the expected fixed duration. assertEquals(expectedOnDuration, effectSegments.get(0).getDuration()); // Vibrator turns off in the middle of the second execution of the first step. Expect it to @@ -567,11 +565,11 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) .compose(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. @@ -584,7 +582,7 @@ public class VibrationThreadTest { waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SETTINGS_UPDATE); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -597,11 +595,11 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setVendorEffectDuration(10 * TEST_TIMEOUT_MILLIS); VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData()); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. @@ -614,7 +612,7 @@ public class VibrationThreadTest { waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SETTINGS_UPDATE); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SETTINGS_UPDATE); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -624,11 +622,11 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); VibrationEffect effect = VibrationEffect.createWaveform(new long[]{100}, new int[]{100}, 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. @@ -641,7 +639,7 @@ public class VibrationThreadTest { waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -650,17 +648,17 @@ public class VibrationThreadTest { mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_THUD); VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_THUD); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_THUD)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); } @Test @@ -671,31 +669,31 @@ public class VibrationThreadTest { HalVibration vibration = createVibration(CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK))); vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback); - long vibrationId = startThreadAndDispatcher(vibration); + startThreadAndDispatcher(vibration); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @Test public void vibrate_singleVibratorPrebakedAndUnsupportedEffect_ignoresVibration() { VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L)); verify(mManagerHooks, never()).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED); - assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty()); + verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); + assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty()); } @Test @@ -704,17 +702,17 @@ public class VibrationThreadTest { mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData()); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(PerformVendorEffectVibratorStep.VENDOR_EFFECT_MAX_DURATION_MS)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertThat(mControllers.get(VIBRATOR_ID).isVibrating()).isFalse(); - assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId)) + assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id)) .containsExactly(effect) .inOrder(); } @@ -730,18 +728,18 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(40L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0), expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f, 0)), - fakeVibrator.getEffectSegments(vibrationId)); + fakeVibrator.getEffectSegments(vibration.id)); } @Test @@ -749,14 +747,14 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .compose(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(0L)); verify(mManagerHooks, never()).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.IGNORED_UNSUPPORTED); - assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty()); + verify(mControllerCallbacks, never()).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.IGNORED_UNSUPPORTED); + assertTrue(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty()); } @Test @@ -774,13 +772,13 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, 0.8f) .compose(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); // Vibrator compose called twice. - verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - assertEquals(3, fakeVibrator.getEffectSegments(vibrationId).size()); + verify(mControllerCallbacks, times(2)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + assertEquals(3, fakeVibrator.getEffectSegments(vibration.id).size()); } @Test @@ -810,14 +808,14 @@ public class VibrationThreadTest { .build()) .addEffect(VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) .compose(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); // Use first duration the vibrator is turned on since we cannot estimate the clicks. verify(mManagerHooks).noteVibratorOn(eq(UID), eq(10L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks, times(5)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( expectedOneShot(10), @@ -829,7 +827,7 @@ public class VibrationThreadTest { expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.7f, /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 120, /* duration= */ 20), expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()); } @@ -851,18 +849,18 @@ public class VibrationThreadTest { .compose(); HalVibration vibration = createVibration(CombinedVibration.createParallel(effect)); vibration.addFallback(VibrationEffect.EFFECT_TICK, fallback); - long vibrationId = startThreadAndDispatcher(vibration); + startThreadAndDispatcher(vibration); waitForCompletion(); // Use first duration the vibrator is turned on since we cannot estimate the clicks. verify(mManagerHooks).noteVibratorOn(eq(UID), anyLong()); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks, times(4)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); List segments = - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id); assertTrue("Wrong segments: " + segments, segments.size() >= 4); assertTrue(segments.get(0) instanceof PrebakedSegment); assertTrue(segments.get(1) instanceof PrimitiveSegment); @@ -891,13 +889,13 @@ public class VibrationThreadTest { .addSustain(Duration.ofMillis(30)) .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200)) .build(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList( expectedRamp(/* amplitude= */ 1, /* frequencyHz= */ 150, /* duration= */ 10), @@ -907,8 +905,8 @@ public class VibrationThreadTest { expectedRamp(/* startAmplitude= */ 0.5f, /* endAmplitude= */ 0.6f, /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 40)), - fakeVibrator.getEffectSegments(vibrationId)); - assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibrationId)); + fakeVibrator.getEffectSegments(vibration.id)); + assertEquals(Arrays.asList(Braking.CLAB), fakeVibrator.getBraking(vibration.id)); } @Test @@ -932,15 +930,15 @@ public class VibrationThreadTest { .addTransition(Duration.ofMillis(40), targetAmplitude(0.7f), targetFrequency(200)) .addTransition(Duration.ofMillis(40), targetAmplitude(0.6f), targetFrequency(200)) .build(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); // Vibrator compose called 3 times with 2 segments instead of 2 times with 3 segments. // Using best split points instead of max-packing PWLEs. - verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - assertEquals(6, fakeVibrator.getEffectSegments(vibrationId).size()); + verify(mControllerCallbacks, times(3)).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + assertEquals(6, fakeVibrator.getEffectSegments(vibration.id).size()); } @Test @@ -949,28 +947,28 @@ public class VibrationThreadTest { fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> fakeVibrator.getAmplitudes().size() > 2, TEST_TIMEOUT_MILLIS)); // Vibration still running after 2 cycles. - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); assertTrue(mControllers.get(VIBRATOR_ID).isVibrating()); mVibrationConductor.binderDied(); waitForCompletion(); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED); } @Test public void vibrate_singleVibrator_skipsSyncedCallbacks() { mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - long vibrationId = startThreadAndDispatcher(VibrationEffect.createOneShot(10, 100)); + HalVibration vibration = startThreadAndDispatcher(VibrationEffect.createOneShot(10, 100)); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); verify(mManagerHooks, never()).prepareSyncedVibration(anyLong(), any()); verify(mManagerHooks, never()).triggerSyncedVibration(anyLong()); verify(mManagerHooks, never()).cancelSyncedVibration(); @@ -984,18 +982,18 @@ public class VibrationThreadTest { .addVibrator(VIBRATOR_ID, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verify(mControllerCallbacks, never()).onComplete(eq(2), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_TICK)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); } @Test @@ -1007,26 +1005,26 @@ public class VibrationThreadTest { CombinedVibration effect = CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); - verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); - verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); assertFalse(mControllers.get(3).isVibrating()); VibrationEffectSegment expected = expectedPrebaked(VibrationEffect.EFFECT_CLICK); assertEquals(Arrays.asList(expected), - mVibratorProviders.get(1).getEffectSegments(vibrationId)); + mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expected), - mVibratorProviders.get(2).getEffectSegments(vibrationId)); + mVibratorProviders.get(2).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expected), - mVibratorProviders.get(3).getEffectSegments(vibrationId)); + mVibratorProviders.get(3).getEffectSegments(vibration.id)); } @Test @@ -1049,32 +1047,32 @@ public class VibrationThreadTest { new long[]{10, 10}, new int[]{1, 2}, -1)) .addVibrator(4, composed) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); - verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); - verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); - verify(mControllerCallbacks).onComplete(eq(4), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(4), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); assertFalse(mControllers.get(3).isVibrating()); assertFalse(mControllers.get(4).isVibrating()); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(1).getEffectSegments(vibrationId)); + mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(2).getEffectSegments(vibrationId)); + mVibratorProviders.get(2).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(2).getAmplitudes()); assertEquals(Arrays.asList(expectedOneShot(20)), - mVibratorProviders.get(3).getEffectSegments(vibrationId)); + mVibratorProviders.get(3).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(1, 2), mVibratorProviders.get(3).getAmplitudes()); assertEquals(Arrays.asList( expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), - mVibratorProviders.get(4).getEffectSegments(vibrationId)); + mVibratorProviders.get(4).getEffectSegments(vibration.id)); } @Test @@ -1094,13 +1092,13 @@ public class VibrationThreadTest { .addNext(1, VibrationEffect.createOneShot(10, 100), /* delay= */ 50) .addNext(2, composed, /* delay= */ 50) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); InOrder controllerVerifier = inOrder(mControllerCallbacks); - controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); - controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); - controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); + controllerVerifier.verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); + controllerVerifier.verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); + controllerVerifier.verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); InOrder batteryVerifier = inOrder(mManagerHooks); batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); @@ -1110,19 +1108,19 @@ public class VibrationThreadTest { batteryVerifier.verify(mManagerHooks).noteVibratorOn(eq(UID), eq(20L)); batteryVerifier.verify(mManagerHooks).noteVibratorOff(eq(UID)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); assertFalse(mControllers.get(3).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(1).getEffectSegments(vibrationId)); + mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes()); assertEquals(Arrays.asList( expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), - mVibratorProviders.get(2).getEffectSegments(vibrationId)); + mVibratorProviders.get(2).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(3).getEffectSegments(vibrationId)); + mVibratorProviders.get(3).getEffectSegments(vibration.id)); } @Test @@ -1143,30 +1141,29 @@ public class VibrationThreadTest { CombinedVibration effect = CombinedVibration.createParallel(composed); // We create the HalVibration here to obtain the vibration id and use it to mock the // required response when calling triggerSyncedVibration. - HalVibration halVibration = createVibration(effect); - long vibrationId = halVibration.id; - when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); - startThreadAndDispatcher(halVibration); + HalVibration vibration = createVibration(effect); + when(mManagerHooks.triggerSyncedVibration(eq(vibration.id))).thenReturn(true); + startThreadAndDispatcher(vibration); assertTrue(waitUntil( - () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty() - && !mVibratorProviders.get(2).getEffectSegments(vibrationId).isEmpty(), + () -> !mVibratorProviders.get(1).getEffectSegments(vibration.id).isEmpty() + && !mVibratorProviders.get(2).getEffectSegments(vibration.id).isEmpty(), TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifySyncedVibrationComplete(); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_COMPOSE; verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks).triggerSyncedVibration(eq(vibration.id)); verify(mManagerHooks, never()).cancelSyncedVibration(); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); VibrationEffectSegment expected = expectedPrimitive( VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); assertEquals(Arrays.asList(expected), - mVibratorProviders.get(1).getEffectSegments(vibrationId)); + mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expected), - mVibratorProviders.get(2).getEffectSegments(vibrationId)); + mVibratorProviders.get(2).getEffectSegments(vibration.id)); } @Test @@ -1190,10 +1187,9 @@ public class VibrationThreadTest { .combine(); // We create the HalVibration here to obtain the vibration id and use it to mock the // required response when calling triggerSyncedVibration. - HalVibration halVibration = createVibration(effect); - long vibrationId = halVibration.id; - when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true); - startThreadAndDispatcher(halVibration); + HalVibration vibration = createVibration(effect); + when(mManagerHooks.triggerSyncedVibration(eq(vibration.id))).thenReturn(true); + startThreadAndDispatcher(vibration); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1204,9 +1200,9 @@ public class VibrationThreadTest { | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM | IVibratorManager.CAP_MIXED_TRIGGER_COMPOSE; verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks).triggerSyncedVibration(eq(vibration.id)); verify(mManagerHooks, never()).cancelSyncedVibration(); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verifyCallbacksTriggered(vibration, Status.FINISHED); } @Test @@ -1221,19 +1217,19 @@ public class VibrationThreadTest { .addVibrator(1, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.createWaveform(new long[]{5}, new int[]{200}, -1)) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC | IVibratorManager.CAP_PREPARE_ON; verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks, never()).triggerSyncedVibration(eq(vibration.id)); verify(mManagerHooks, never()).cancelSyncedVibration(); assertEquals(Arrays.asList(expectedOneShot(10)), - mVibratorProviders.get(1).getEffectSegments(vibrationId)); + mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(100), mVibratorProviders.get(1).getAmplitudes()); assertEquals(Arrays.asList(expectedOneShot(5)), - mVibratorProviders.get(2).getEffectSegments(vibrationId)); + mVibratorProviders.get(2).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(200), mVibratorProviders.get(2).getAmplitudes()); } @@ -1250,10 +1246,9 @@ public class VibrationThreadTest { .combine(); // We create the HalVibration here to obtain the vibration id and use it to mock the // required response when calling triggerSyncedVibration. - HalVibration halVibration = createVibration(effect); - long vibrationId = halVibration.id; - when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(false); - startThreadAndDispatcher(halVibration); + HalVibration vibration = createVibration(effect); + when(mManagerHooks.triggerSyncedVibration(eq(vibration.id))).thenReturn(false); + startThreadAndDispatcher(vibration); waitForCompletion(); long expectedCap = IVibratorManager.CAP_SYNC @@ -1262,7 +1257,7 @@ public class VibrationThreadTest { | IVibratorManager.CAP_MIXED_TRIGGER_ON | IVibratorManager.CAP_MIXED_TRIGGER_PERFORM; verify(mManagerHooks).prepareSyncedVibration(eq(expectedCap), eq(vibratorIds)); - verify(mManagerHooks).triggerSyncedVibration(eq(vibrationId)); + verify(mManagerHooks).triggerSyncedVibration(eq(vibration.id)); verify(mManagerHooks).cancelSyncedVibration(); assertTrue(mVibratorProviders.get(1).getAmplitudes().isEmpty()); } @@ -1282,7 +1277,7 @@ public class VibrationThreadTest { .addVibrator(3, VibrationEffect.createWaveform( new long[]{60}, new int[]{6}, -1)) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); // All vibrators are turned on in parallel. assertTrue(waitUntil( @@ -1295,20 +1290,20 @@ public class VibrationThreadTest { verify(mManagerHooks).noteVibratorOn(eq(UID), eq(80L)); verify(mManagerHooks).noteVibratorOff(eq(UID)); - verify(mControllerCallbacks).onComplete(eq(1), eq(vibrationId)); - verify(mControllerCallbacks).onComplete(eq(2), eq(vibrationId)); - verify(mControllerCallbacks).onComplete(eq(3), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(1), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(2), eq(vibration.id)); + verify(mControllerCallbacks).onComplete(eq(3), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); assertFalse(mControllers.get(3).isVibrating()); assertEquals(Arrays.asList(expectedOneShot(25)), - mVibratorProviders.get(1).getEffectSegments(vibrationId)); + mVibratorProviders.get(1).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expectedOneShot(80)), - mVibratorProviders.get(2).getEffectSegments(vibrationId)); + mVibratorProviders.get(2).getEffectSegments(vibration.id)); assertEquals(Arrays.asList(expectedOneShot(60)), - mVibratorProviders.get(3).getEffectSegments(vibrationId)); + mVibratorProviders.get(3).getEffectSegments(vibration.id)); assertEquals(expectedAmplitudes(1, 2, 3), mVibratorProviders.get(1).getAmplitudes()); assertEquals(expectedAmplitudes(4, 5), mVibratorProviders.get(2).getAmplitudes()); assertEquals(expectedAmplitudes(6), mVibratorProviders.get(3).getAmplitudes()); @@ -1327,17 +1322,11 @@ public class VibrationThreadTest { CombinedVibration.createParallel( VibrationEffect.createOneShot( expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE))); - CountDownLatch vibrationCompleteLatch = new CountDownLatch(1); - doAnswer(unused -> { - vibrationCompleteLatch.countDown(); - return null; - }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any()); startThreadAndDispatcher(vibration); long startTime = SystemClock.elapsedRealtime(); - assertTrue(vibrationCompleteLatch.await(expectedDuration + TEST_TIMEOUT_MILLIS, - TimeUnit.MILLISECONDS)); + vibration.waitForEnd(); long vibrationEndTime = SystemClock.elapsedRealtime(); waitForCompletion(rampDownDuration + TEST_TIMEOUT_MILLIS); @@ -1363,17 +1352,11 @@ public class VibrationThreadTest { CombinedVibration.createParallel( VibrationEffect.createOneShot( expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE))); - CountDownLatch vibrationCompleteLatch = new CountDownLatch(1); - doAnswer(unused -> { - vibrationCompleteLatch.countDown(); - return null; - }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any()); startThreadAndDispatcher(vibration); long startTime = SystemClock.elapsedRealtime(); - assertTrue(vibrationCompleteLatch.await(callbackDelay + TEST_TIMEOUT_MILLIS, - TimeUnit.MILLISECONDS)); + vibration.waitForEnd(); long vibrationEndTime = SystemClock.elapsedRealtime(); waitForCompletion(TEST_TIMEOUT_MILLIS); @@ -1397,17 +1380,11 @@ public class VibrationThreadTest { CombinedVibration.createParallel( VibrationEffect.createOneShot( expectedDuration, VibrationEffect.DEFAULT_AMPLITUDE))); - CountDownLatch vibrationCompleteLatch = new CountDownLatch(1); - doAnswer(unused -> { - vibrationCompleteLatch.countDown(); - return null; - }).when(mManagerHooks).onVibrationCompleted(eq(vibration.id), any()); startThreadAndDispatcher(vibration); long startTime = SystemClock.elapsedRealtime(); - assertTrue(vibrationCompleteLatch.await(callbackTimeout + TEST_TIMEOUT_MILLIS, - TimeUnit.MILLISECONDS)); + vibration.waitForEnd(); long vibrationEndTime = SystemClock.elapsedRealtime(); waitForCompletion(callbackDelay + TEST_TIMEOUT_MILLIS); @@ -1461,11 +1438,11 @@ public class VibrationThreadTest { fakeVibrator.setOnLatency(latency); VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); - assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibrationId).isEmpty(), + assertTrue(waitUntil(() -> !fakeVibrator.getEffectSegments(vibration.id).isEmpty(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(cancellingThread). @@ -1480,7 +1457,7 @@ public class VibrationThreadTest { // After the vibrator call ends the vibration is cancelled and the vibrator is turned off. waitForCompletion(/* timeout= */ latency + TEST_TIMEOUT_MILLIS); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -1500,10 +1477,10 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f, 100) .compose()) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. @@ -1516,7 +1493,7 @@ public class VibrationThreadTest { waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } @@ -1534,10 +1511,10 @@ public class VibrationThreadTest { .addVibrator(1, VibrationEffect.createVendorEffect(createTestVendorData())) .addVibrator(2, VibrationEffect.createVendorEffect(createTestVendorData())) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. @@ -1550,7 +1527,7 @@ public class VibrationThreadTest { waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } @@ -1566,12 +1543,12 @@ public class VibrationThreadTest { new long[]{100, 100}, new int[]{1, 2}, 0)) .addVibrator(2, VibrationEffect.createOneShot(100, 100)) .combine(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(1).isVibrating() && mControllers.get(2).isVibrating(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Run cancel in a separate thread so if VibrationThread.cancel blocks then this test should // fail at waitForCompletion(vibrationThread) if the vibration not cancelled immediately. @@ -1584,7 +1561,7 @@ public class VibrationThreadTest { waitForCompletion(/* timeout= */ 50); cancellingThread.join(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_SCREEN_OFF); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_SCREEN_OFF); assertFalse(mControllers.get(1).isVibrating()); assertFalse(mControllers.get(2).isVibrating()); } @@ -1592,19 +1569,17 @@ public class VibrationThreadTest { @Test public void vibrate_binderDied_cancelsVibration() throws Exception { VibrationEffect effect = VibrationEffect.createWaveform(new long[]{5}, new int[]{100}, 0); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); mVibrationConductor.binderDied(); waitForCompletion(); - verify(mVibrationToken).linkToDeath(same(mVibrationConductor), eq(0)); - verify(mVibrationToken).unlinkToDeath(same(mVibrationConductor), eq(0)); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BINDER_DIED); - assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId).isEmpty()); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED); + assertFalse(mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id).isEmpty()); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); } @@ -1615,15 +1590,15 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.createWaveform( new long[]{5, 5, 5}, new int[]{60, 120, 240}, -1); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); // Duration extended for 5 + 5 + 5 + 15. assertEquals(Arrays.asList(expectedOneShot(30)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); List amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes(); assertTrue(amplitudes.size() > 3); assertEquals(expectedAmplitudes(60, 120, 240), amplitudes.subList(0, 3)); @@ -1633,24 +1608,24 @@ public class VibrationThreadTest { } @Test - public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() { + public void vibrate_waveformWithRampDown_triggersCallbackWhenOriginalVibrationEnds() + throws Exception { when(mVibrationConfigMock.getRampDownDurationMs()).thenReturn(10_000); mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibrationEffect effect = VibrationEffect.createOneShot(10, 200); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); // Vibration completed but vibrator not yet released. - verify(mManagerHooks, timeout(TEST_TIMEOUT_MILLIS)).onVibrationCompleted(eq(vibrationId), - eq(new Vibration.EndInfo(Status.FINISHED))); + vibration.waitForEnd(); verify(mManagerHooks, never()).onVibrationThreadReleased(anyLong()); // Thread still running ramp down. - assertTrue(mThread.isRunningVibrationId(vibrationId)); + assertTrue(mThread.isRunningVibrationId(vibration.id)); // Duration extended for 10 + 10000. assertEquals(Arrays.asList(expectedOneShot(10_010)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); // Will stop the ramp down right away. mVibrationConductor.notifyCancelled( @@ -1658,9 +1633,8 @@ public class VibrationThreadTest { waitForCompletion(); // Does not cancel already finished vibration, but releases vibrator. - verify(mManagerHooks, never()).onVibrationCompleted(eq(vibrationId), - eq(new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE))); - verify(mManagerHooks).onVibrationThreadReleased(vibrationId); + assertThat(vibration.getStatus()).isNotEqualTo(Status.CANCELLED_BY_SETTINGS_UPDATE); + verify(mManagerHooks).onVibrationThreadReleased(vibration.id); } @Test @@ -1670,18 +1644,18 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibrationEffect effect = VibrationEffect.createOneShot(10_000, 240); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); assertTrue(waitUntil(() -> mControllers.get(VIBRATOR_ID).isVibrating(), TEST_TIMEOUT_MILLIS)); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false); waitForCompletion(); - verifyCallbacksTriggered(vibrationId, Status.CANCELLED_BY_USER); + verifyCallbacksTriggered(vibration, Status.CANCELLED_BY_USER); // Duration extended for 10000 + 15. assertEquals(Arrays.asList(expectedOneShot(10_015)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); List amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes(); assertTrue(amplitudes.size() > 1); for (int i = 1; i < amplitudes.size(); i++) { @@ -1696,14 +1670,14 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(VibrationEffect.EFFECT_CLICK); VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty()); } @@ -1714,13 +1688,13 @@ public class VibrationThreadTest { mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_PERFORM_VENDOR_EFFECTS); VibrationEffect effect = VibrationEffect.createVendorEffect(createTestVendorData()); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); - assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibrationId)) + assertThat(mVibratorProviders.get(VIBRATOR_ID).getVendorEffects(vibration.id)) .containsExactly(effect) .inOrder(); assertThat(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes()).isEmpty(); @@ -1737,15 +1711,15 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) .compose(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertEquals( Arrays.asList(expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), - mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId)); + mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibration.id)); assertTrue(mVibratorProviders.get(VIBRATOR_ID).getAmplitudes().isEmpty()); } @@ -1764,14 +1738,14 @@ public class VibrationThreadTest { VibrationEffect effect = VibrationEffect.startWaveform() .addTransition(Duration.ofMillis(1), targetAmplitude(1)) .build(); - long vibrationId = startThreadAndDispatcher(effect); + HalVibration vibration = startThreadAndDispatcher(effect); waitForCompletion(); - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId)); - verifyCallbacksTriggered(vibrationId, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id)); + verifyCallbacksTriggered(vibration, Status.FINISHED); assertEquals(Arrays.asList(expectedRamp(0, 1, 150, 150, 1)), - fakeVibrator.getEffectSegments(vibrationId)); + fakeVibrator.getEffectSegments(vibration.id)); assertTrue(fakeVibrator.getAmplitudes().isEmpty()); } @@ -1796,69 +1770,68 @@ public class VibrationThreadTest { VibrationEffect effect4 = VibrationEffect.createOneShot(8000, 100); VibrationEffect effect5 = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - long vibrationId1 = startThreadAndDispatcher(effect1); + HalVibration vibration1 = startThreadAndDispatcher(effect1); waitForCompletion(); - verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1); - verifyCallbacksTriggered(vibrationId1, Status.FINISHED); - long vibrationId2 = startThreadAndDispatcher(effect2); + HalVibration vibration2 = startThreadAndDispatcher(effect2); // Effect2 won't complete on its own. Cancel it after a couple of repeats. Thread.sleep(150); // More than two TICKs. mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_USER), /* immediate= */ false); waitForCompletion(); - long vibrationId3 = startThreadAndDispatcher(effect3); + HalVibration vibration3 = startThreadAndDispatcher(effect3); waitForCompletion(); // Effect4 is a long oneshot, but it gets cancelled as fast as possible. long start4 = System.currentTimeMillis(); - long vibrationId4 = startThreadAndDispatcher(effect4); + HalVibration vibration4 = startThreadAndDispatcher(effect4); mVibrationConductor.notifyCancelled( new Vibration.EndInfo(Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ true); waitForCompletion(); long duration4 = System.currentTimeMillis() - start4; // Effect5 is to show that things keep going after the immediate cancel. - long vibrationId5 = startThreadAndDispatcher(effect5); + HalVibration vibration5 = startThreadAndDispatcher(effect5); waitForCompletion(); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); // Effect1 - verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibrationId1); - verifyCallbacksTriggered(vibrationId1, Status.FINISHED); + verify(mControllerCallbacks).onComplete(VIBRATOR_ID, vibration1.id); + verifyCallbacksTriggered(vibration1, Status.FINISHED); assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - fakeVibrator.getEffectSegments(vibrationId1)); + fakeVibrator.getEffectSegments(vibration1.id)); // Effect2: repeating, cancelled. - verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibrationId2); - verifyCallbacksTriggered(vibrationId2, Status.CANCELLED_BY_USER); + verify(mControllerCallbacks, atLeast(2)).onComplete(VIBRATOR_ID, vibration2.id); + verifyCallbacksTriggered(vibration2, Status.CANCELLED_BY_USER); // The exact count of segments might vary, so just check that there's more than 2 and // all elements are the same segment. - List actualSegments2 = fakeVibrator.getEffectSegments(vibrationId2); + List actualSegments2 = + fakeVibrator.getEffectSegments(vibration2.id); assertTrue(actualSegments2.size() + " > 2", actualSegments2.size() > 2); for (VibrationEffectSegment segment : actualSegments2) { assertEquals(expectedPrebaked(VibrationEffect.EFFECT_TICK), segment); } // Effect3 - verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibrationId3)); - verifyCallbacksTriggered(vibrationId3, Status.FINISHED); + verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration3.id)); + verifyCallbacksTriggered(vibration3, Status.FINISHED); assertEquals(Arrays.asList( expectedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 0)), - fakeVibrator.getEffectSegments(vibrationId3)); + fakeVibrator.getEffectSegments(vibration3.id)); // Effect4: cancelled quickly. - verifyCallbacksTriggered(vibrationId4, Status.CANCELLED_BY_SCREEN_OFF); + verifyCallbacksTriggered(vibration4, Status.CANCELLED_BY_SCREEN_OFF); assertTrue("Tested duration=" + duration4, duration4 < 2000); // Effect5: played normally after effect4, which may or may not have played. assertEquals(Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK)), - fakeVibrator.getEffectSegments(vibrationId5)); + fakeVibrator.getEffectSegments(vibration5.id)); } private void mockVibrators(int... vibratorIds) { @@ -1875,19 +1848,19 @@ public class VibrationThreadTest { mVibrationSettings.mSettingObserver.onChange(false); } - private long startThreadAndDispatcher(VibrationEffect effect) { + private HalVibration startThreadAndDispatcher(VibrationEffect effect) { return startThreadAndDispatcher(CombinedVibration.createParallel(effect)); } - private long startThreadAndDispatcher(CombinedVibration effect) { + private HalVibration startThreadAndDispatcher(CombinedVibration effect) { return startThreadAndDispatcher(createVibration(effect)); } - private long startThreadAndDispatcher(HalVibration vib) { + private HalVibration startThreadAndDispatcher(HalVibration vib) { return startThreadAndDispatcher(vib, /* requestVibrationParamsFuture= */ null); } - private long startThreadAndDispatcher(VibrationEffect effect, + private HalVibration startThreadAndDispatcher(VibrationEffect effect, CompletableFuture requestVibrationParamsFuture, int usage) { VibrationAttributes attrs = new VibrationAttributes.Builder() .setUsage(usage) @@ -1898,14 +1871,14 @@ public class VibrationThreadTest { return startThreadAndDispatcher(vib, requestVibrationParamsFuture); } - private long startThreadAndDispatcher(HalVibration vib, + private HalVibration startThreadAndDispatcher(HalVibration vib, CompletableFuture requestVibrationParamsFuture) { mControllers = createVibratorControllers(); DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers); mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter, mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture, mManagerHooks); assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor)); - return mVibrationConductor.getVibration().id; + return mVibrationConductor.getVibration(); } private boolean waitUntil(BooleanSupplier predicate, long timeout) @@ -1994,13 +1967,9 @@ public class VibrationThreadTest { .collect(Collectors.toList()); } - private void verifyCallbacksTriggered(long vibrationId, Status expectedStatus) { - verifyCallbacksTriggered(vibrationId, new Vibration.EndInfo(expectedStatus)); - } - - private void verifyCallbacksTriggered(long vibrationId, Vibration.EndInfo expectedEndInfo) { - verify(mManagerHooks).onVibrationCompleted(eq(vibrationId), eq(expectedEndInfo)); - verify(mManagerHooks).onVibrationThreadReleased(vibrationId); + private void verifyCallbacksTriggered(HalVibration vibration, Status expectedStatus) { + assertThat(vibration.getStatus()).isEqualTo(expectedStatus); + verify(mManagerHooks).onVibrationThreadReleased(vibration.id); } private static final class TestLooperAutoDispatcher extends Thread { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java index 0d13be6d5ab2ecd98b1bf94ab0958096b766f895..e8ca8bf8ec636cc7126aee36794e710be1cc71ea 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java @@ -127,13 +127,13 @@ public class VibratorControllerTest { public void setExternalControl_withCapability_enablesExternalControl() { mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); VibratorController controller = createController(); - assertFalse(controller.isUnderExternalControl()); + assertFalse(controller.isVibrating()); controller.setExternalControl(true); - assertTrue(controller.isUnderExternalControl()); + assertTrue(controller.isVibrating()); controller.setExternalControl(false); - assertFalse(controller.isUnderExternalControl()); + assertFalse(controller.isVibrating()); InOrder inOrderVerifier = inOrder(mNativeWrapperMock); inOrderVerifier.verify(mNativeWrapperMock).setExternalControl(eq(true)); @@ -143,10 +143,10 @@ public class VibratorControllerTest { @Test public void setExternalControl_withNoCapability_ignoresExternalControl() { VibratorController controller = createController(); - assertFalse(controller.isUnderExternalControl()); + assertFalse(controller.isVibrating()); controller.setExternalControl(true); - assertFalse(controller.isUnderExternalControl()); + assertFalse(controller.isVibrating()); verify(mNativeWrapperMock, never()).setExternalControl(anyBoolean()); } @@ -180,6 +180,38 @@ public class VibratorControllerTest { verify(mNativeWrapperMock, never()).alwaysOnEnable(anyLong(), anyLong(), anyLong()); } + @Test + public void setAmplitude_vibratorIdle_ignoresAmplitude() { + VibratorController controller = createController(); + assertFalse(controller.isVibrating()); + + controller.setAmplitude(1); + assertEquals(0, controller.getCurrentAmplitude(), /* delta= */ 0); + } + + @Test + public void setAmplitude_vibratorUnderExternalControl_ignoresAmplitude() { + mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + VibratorController controller = createController(); + controller.setExternalControl(true); + assertTrue(controller.isVibrating()); + + controller.setAmplitude(1); + assertEquals(0, controller.getCurrentAmplitude(), /* delta= */ 0); + } + + @Test + public void setAmplitude_vibratorVibrating_setsAmplitude() { + when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); + VibratorController controller = createController(); + controller.on(100, /* vibrationId= */ 1); + assertTrue(controller.isVibrating()); + assertEquals(-1, controller.getCurrentAmplitude(), /* delta= */ 0); + + controller.setAmplitude(1); + assertEquals(1, controller.getCurrentAmplitude(), /* delta= */ 0); + } + @Test public void on_withDuration_turnsVibratorOn() { when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0)); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 4012575cda8870c1a5c9d31b760ae76ae306d5a6..538c3fc2ddaec948177594802083f79c8eabe45e 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -266,12 +266,13 @@ public class VibratorManagerServiceTest { @After public void tearDown() throws Exception { if (mService != null) { - if (!mPendingVibrations.stream().allMatch(HalVibration::hasEnded)) { - // Cancel any pending vibration from tests. - cancelVibrate(mService); - for (HalVibration vibration : mPendingVibrations) { - vibration.waitForEnd(); - } + // Make sure we have permission to cancel test vibrations, even if the test denied them. + grantPermission(android.Manifest.permission.VIBRATE); + // Cancel any pending vibration from tests, including external vibrations. + cancelVibrate(mService); + // Wait until pending vibrations end asynchronously. + for (HalVibration vibration : mPendingVibrations) { + vibration.waitForEnd(); } // Wait until all vibrators have stopped vibrating, waiting for ramp-down. // Note: if a test is flaky here something is wrong with the vibration finalization. @@ -1538,7 +1539,6 @@ public class VibratorManagerServiceTest { PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0); assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId()); VibrationAttributes attrs = vibration.callerInfo.attrs; - assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrs.getUsage()); assertTrue(attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)); assertTrue(attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); } @@ -1560,11 +1560,11 @@ public class VibratorManagerServiceTest { HapticFeedbackConstants.SCROLL_TICK, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); mHapticFeedbackVibrationMapSourceTouchScreen.put( - HapticFeedbackConstants.DRAG_START, - VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)); + HapticFeedbackConstants.SCROLL_ITEM_FOCUS, + VibrationEffect.createPredefined(VibrationEffect.EFFECT_THUD)); mockVibrators(1); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); - fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_TICK); + fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_THUD); VibratorManagerService service = createSystemReadyService(); HalVibration vibrationByRotary = @@ -1573,7 +1573,7 @@ public class VibratorManagerServiceTest { InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true); HalVibration vibrationByTouchScreen = performHapticFeedbackForInputDeviceAndWaitUntilFinished( - service, HapticFeedbackConstants.DRAG_START, /* inputDeviceId= */ 0, + service, HapticFeedbackConstants.SCROLL_ITEM_FOCUS, /* inputDeviceId= */ 0, InputDevice.SOURCE_TOUCHSCREEN, /* always= */ true); List playedSegments = fakeVibrator.getAllEffectSegments(); @@ -1583,18 +1583,17 @@ public class VibratorManagerServiceTest { PrebakedSegment segmentByRotary = (PrebakedSegment) playedSegments.get(0); assertEquals(VibrationEffect.EFFECT_CLICK, segmentByRotary.getEffectId()); VibrationAttributes attrsByRotary = vibrationByRotary.callerInfo.attrs; - assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrsByRotary.getUsage()); assertTrue(attrsByRotary.isFlagSet( VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)); assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); // Verify feedback by touch screen input PrebakedSegment segmentByTouchScreen = (PrebakedSegment) playedSegments.get(1); - assertEquals(VibrationEffect.EFFECT_TICK, segmentByTouchScreen.getEffectId()); + assertEquals(VibrationEffect.EFFECT_THUD, segmentByTouchScreen.getEffectId()); VibrationAttributes attrsByTouchScreen = vibrationByTouchScreen.callerInfo.attrs; - assertEquals(VibrationAttributes.USAGE_TOUCH, attrsByTouchScreen.getUsage()); - assertTrue(attrsByRotary.isFlagSet( + assertTrue(attrsByTouchScreen.isFlagSet( VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)); - assertTrue(attrsByRotary.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); + assertTrue( + attrsByTouchScreen.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)); } @Test @@ -2244,7 +2243,7 @@ public class VibratorManagerServiceTest { VibratorManagerService service = createSystemReadyService(); VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100); - vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); + HalVibration vibration = vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS); // VibrationThread will start this vibration async, so wait until vibration is triggered. assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); @@ -2257,7 +2256,8 @@ public class VibratorManagerServiceTest { assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel); // Vibration is cancelled. - assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); + vibration.waitForEnd(); + assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED); assertEquals(Arrays.asList(false, true), mVibratorProviders.get(1).getExternalControlStates()); } @@ -2298,7 +2298,7 @@ public class VibratorManagerServiceTest { VibrationEffect repeatingEffect = VibrationEffect.createWaveform( new long[]{100, 200, 300}, new int[]{128, 255, 255}, 1); - vibrate(service, repeatingEffect, ALARM_ATTRS); + HalVibration repeatingVibration = vibrate(service, repeatingEffect, ALARM_ATTRS); // VibrationThread will start this vibration async, so wait until vibration is triggered. assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); @@ -2310,7 +2310,8 @@ public class VibratorManagerServiceTest { assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel); // Vibration is cancelled. - assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); + repeatingVibration.waitForEnd(); + assertThat(repeatingVibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED); assertEquals(Arrays.asList(false, true), mVibratorProviders.get(1).getExternalControlStates()); } diff --git a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java index 1c331166d317e290446245f668dc5a05200ceeb8..6f9c8904ca32ec1f825f036aeec82c4ee0b911ef 100644 --- a/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/CombinationKeyTests.java @@ -22,6 +22,7 @@ import static android.view.KeyEvent.KEYCODE_VOLUME_UP; import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS; import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE; +import android.platform.test.annotations.DisableFlags; import android.view.ViewConfiguration; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -39,6 +40,7 @@ import org.junit.runner.RunWith; */ @MediumTest @RunWith(AndroidJUnit4.class) +@DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES) public class CombinationKeyTests extends ShortcutKeyTestBase { private static final long A11Y_KEY_HOLD_MILLIS = 3500; diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java index 487390d4411d9bf934b1af173afa27734a009f80..8b5f68a1e974174fc479eba170cbd07db045b3d6 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyCombinationManagerTests.java @@ -71,12 +71,12 @@ public class KeyCombinationManagerTests { new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { @Override - void execute() { + public void execute() { mAction1Triggered.countDown(); } @Override - void cancel() { + public void cancel() { } }); @@ -85,21 +85,21 @@ public class KeyCombinationManagerTests { new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP) { @Override - boolean preCondition() { + public boolean preCondition() { return mPreCondition; } @Override - void execute() { + public void execute() { mAction2Triggered.countDown(); } @Override - void cancel() { + public void cancel() { } @Override - long getKeyInterceptDelayMs() { + public long getKeyInterceptDelayMs() { return 0; } }); @@ -115,12 +115,12 @@ public class KeyCombinationManagerTests { }; @Override - void execute() { + public void execute() { mHandler.postDelayed(mAction, SCHEDULE_TIME); } @Override - void cancel() { + public void cancel() { mHandler.removeCallbacks(mAction); } }); @@ -235,12 +235,12 @@ public class KeyCombinationManagerTests { new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { @Override - void execute() { + public void execute() { mAction1Triggered.countDown(); } @Override - void cancel() { + public void cancel() { } }; diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java index 3d978e424375d9326b5b8c3b831a56e5e60a89aa..cdb45423c11ac04a07c0af55e29c4d5fab9df695 100644 --- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java @@ -22,10 +22,13 @@ import static com.android.server.policy.PhoneWindowManager.DOUBLE_TAP_HOME_RECEN import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ALL_APPS; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_ASSIST; import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL; +import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS; +import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAVIOR_MUTE; import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL; import android.hardware.input.KeyGestureEvent; import android.os.RemoteException; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.KeyEvent; @@ -56,7 +59,117 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT); @Keep - private static Object[][] shortcutTestArguments() { + private static Object[][] shortcutTestArgumentsNotMigratedToKeyGestureController() { + // testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState + return new Object[][]{ + {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, + KeyGestureEvent.KEY_GESTURE_TYPE_HOME, + KeyEvent.KEYCODE_HOME, 0}, + {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, + KeyEvent.KEYCODE_BACK, 0}, + {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP}, + KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP, + KeyEvent.KEYCODE_VOLUME_UP, 0}, + {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN}, + KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN, + KeyEvent.KEYCODE_VOLUME_DOWN, 0}, + {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE}, + KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE, + KeyEvent.KEYCODE_VOLUME_MUTE, 0}, + {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE}, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, + 0}, + {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER}, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER, + 0}, + {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER}, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, + KeyEvent.KEYCODE_TV_POWER, 0}, + {"SYSTEM_NAVIGATION_DOWN key -> System Navigation", + new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN}, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, + KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN, + 0}, + {"SYSTEM_NAVIGATION_UP key -> System Navigation", + new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP}, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, + KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP, + 0}, + {"SYSTEM_NAVIGATION_LEFT key -> System Navigation", + new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT}, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, + KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT, + 0}, + {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation", + new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT}, + KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, + KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0}, + {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP}, + KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SLEEP, 0}, + {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP}, + KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, + 0}, + {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP}, + KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0}, + {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY}, + KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, + KeyEvent.KEYCODE_MEDIA_PLAY, 0}, + {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE}, + KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, + KeyEvent.KEYCODE_MEDIA_PAUSE, 0}, + {"MEDIA_PLAY_PAUSE key -> Media Control", + new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, + KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}, + {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, + KeyEvent.KEYCODE_B, META_ON}, + {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, + KeyEvent.KEYCODE_EXPLORER, 0}, + {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, + KeyEvent.KEYCODE_C, META_ON}, + {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, + KeyEvent.KEYCODE_CONTACTS, 0}, + {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, + KeyEvent.KEYCODE_E, META_ON}, + {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, + KeyEvent.KEYCODE_ENVELOPE, 0}, + {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, + KeyEvent.KEYCODE_K, META_ON}, + {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, + KeyEvent.KEYCODE_CALENDAR, 0}, + {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, + KeyEvent.KEYCODE_P, META_ON}, + {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, + KeyEvent.KEYCODE_MUSIC, 0}, + {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, + KeyEvent.KEYCODE_U, META_ON}, + {"CALCULATOR key -> Launch Default Calculator", + new int[]{KeyEvent.KEYCODE_CALCULATOR}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, + KeyEvent.KEYCODE_CALCULATOR, 0}, + {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS, + KeyEvent.KEYCODE_M, META_ON}, + {"Meta + S -> Launch Default Messaging App", + new int[]{META_KEY, KeyEvent.KEYCODE_S}, + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING, + KeyEvent.KEYCODE_S, META_ON}}; + } + + @Keep + private static Object[][] shortcutTestArgumentsMigratedToKeyGestureController() { // testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState return new Object[][]{ {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H}, @@ -64,9 +177,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_ENTER, META_ON}, - {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME}, - KeyGestureEvent.KEY_GESTURE_TYPE_HOME, - KeyEvent.KEYCODE_HOME, 0}, {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS}, KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_RECENT_APPS, 0}, @@ -76,9 +186,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB}, KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON}, - {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, - KeyGestureEvent.KEY_GESTURE_TYPE_BACK, - KeyEvent.KEYCODE_BACK, 0}, {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE}, KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_ESCAPE, META_ON}, @@ -138,15 +245,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE}, KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE, KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0}, - {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP}, - KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP, - KeyEvent.KEYCODE_VOLUME_UP, 0}, - {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN}, - KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN, - KeyEvent.KEYCODE_VOLUME_DOWN, 0}, - {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE}, - KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE, - KeyEvent.KEYCODE_VOLUME_MUTE, 0}, {"ALL_APPS key -> Open App Drawer", new int[]{KeyEvent.KEYCODE_ALL_APPS}, KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, @@ -170,9 +268,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK}, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, KeyEvent.KEYCODE_CAPS_LOCK, 0}, - {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE}, - KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE, - 0}, {"Meta + Ctrl + DPAD_UP -> Split screen navigation", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP}, KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION, @@ -194,92 +289,6 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N}, KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N, META_ON | CTRL_ON}, - {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER}, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER, - 0}, - {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER}, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, - KeyEvent.KEYCODE_TV_POWER, 0}, - {"SYSTEM_NAVIGATION_DOWN key -> System Navigation", - new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN}, - KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, - KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN, - 0}, - {"SYSTEM_NAVIGATION_UP key -> System Navigation", - new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP}, - KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, - KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP, - 0}, - {"SYSTEM_NAVIGATION_LEFT key -> System Navigation", - new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT}, - KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, - KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT, - 0}, - {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation", - new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT}, - KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION, - KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0}, - {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP}, - KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SLEEP, 0}, - {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP}, - KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP, - 0}, - {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP}, - KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0}, - {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY}, - KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, - KeyEvent.KEYCODE_MEDIA_PLAY, 0}, - {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE}, - KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, - KeyEvent.KEYCODE_MEDIA_PAUSE, 0}, - {"MEDIA_PLAY_PAUSE key -> Media Control", - new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE}, - KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY, - KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}, - {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, - KeyEvent.KEYCODE_B, META_ON}, - {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER, - KeyEvent.KEYCODE_EXPLORER, 0}, - {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, - KeyEvent.KEYCODE_C, META_ON}, - {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS, - KeyEvent.KEYCODE_CONTACTS, 0}, - {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, - KeyEvent.KEYCODE_E, META_ON}, - {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL, - KeyEvent.KEYCODE_ENVELOPE, 0}, - {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, - KeyEvent.KEYCODE_K, META_ON}, - {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR, - KeyEvent.KEYCODE_CALENDAR, 0}, - {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, - KeyEvent.KEYCODE_P, META_ON}, - {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC, - KeyEvent.KEYCODE_MUSIC, 0}, - {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, - KeyEvent.KEYCODE_U, META_ON}, - {"CALCULATOR key -> Launch Default Calculator", - new int[]{KeyEvent.KEYCODE_CALCULATOR}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR, - KeyEvent.KEYCODE_CALCULATOR, 0}, - {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS, - KeyEvent.KEYCODE_M, META_ON}, - {"Meta + S -> Launch Default Messaging App", - new int[]{META_KEY, KeyEvent.KEYCODE_S}, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING, - KeyEvent.KEYCODE_S, META_ON}, {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN}, KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE, @@ -296,72 +305,14 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL, KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, KeyEvent.KEYCODE_HOME, 0}, - {"Long press META + ENTER -> Toggle Notification panel", - new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, - LONG_PRESS_HOME_NOTIFICATION_PANEL, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, - KeyEvent.KEYCODE_ENTER, - META_ON}, - {"Long press META + H -> Toggle Notification panel", - new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, - KeyEvent.KEYCODE_H, META_ON}, {"Long press HOME key -> Launch assistant", new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST, KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_HOME, 0}, - {"Long press META + ENTER -> Launch assistant", - new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, - KeyEvent.KEYCODE_ENTER, META_ON}, - {"Long press META + H -> Launch assistant", - new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST, - KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H, - META_ON}, {"Long press HOME key -> Open App Drawer", new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS, KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, - KeyEvent.KEYCODE_HOME, 0}, - {"Long press META + ENTER -> Open App Drawer", - new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS, - KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, - KeyEvent.KEYCODE_ENTER, META_ON}, - {"Long press META + H -> Open App Drawer", - new int[]{META_KEY, KeyEvent.KEYCODE_H}, - LONG_PRESS_HOME_ALL_APPS, - KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS, - KeyEvent.KEYCODE_H, META_ON}}; - } - - @Keep - private static Object[][] doubleTapOnHomeTestArguments() { - // testName, testKeys, doubleTapOnHomeBehavior, expectedKeyGestureType, expectedKey, - // expectedModifierState - return new Object[][]{ - {"Double tap HOME -> Open App switcher", - new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, - KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_HOME, - 0}, - {"Double tap META + ENTER -> Open App switcher", - new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, - DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, - KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, - KeyEvent.KEYCODE_ENTER, META_ON}, - {"Double tap META + H -> Open App switcher", - new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI, - KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_H, - META_ON}}; - } - - @Keep - private static Object[][] settingsKeyTestArguments() { - // testName, testKeys, settingsKeyBehavior, expectedKeyGestureType, expectedKey, - // expectedModifierState - return new Object[][]{ - {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS}, - SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL, - KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, - KeyEvent.KEYCODE_SETTINGS, 0}}; + KeyEvent.KEYCODE_HOME, 0}}; } @Before @@ -381,8 +332,18 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - @Parameters(method = "shortcutTestArguments") - public void testShortcut(String testName, int[] testKeys, + @Parameters(method = "shortcutTestArgumentsNotMigratedToKeyGestureController") + public void testShortcuts_notMigratedToKeyGestureController(String testName, + int[] testKeys, @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, + int expectedKey, int expectedModifierState) { + testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey, + expectedModifierState); + } + + @Test + @Parameters(method = "shortcutTestArgumentsMigratedToKeyGestureController") + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + public void testShortcuts_migratedToKeyGestureController(String testName, int[] testKeys, @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, int expectedModifierState) { testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey, @@ -402,31 +363,29 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { } @Test - @Parameters(method = "doubleTapOnHomeTestArguments") - public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys, - int doubleTapOnHomeBehavior, - @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, - int expectedModifierState) { - mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior); - sendKeyCombination(testKeys, 0 /* duration */); - sendKeyCombination(testKeys, 0 /* duration */); + public void testDoubleTapOnHomeBehavior_AppSwitchBehavior() { + mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(DOUBLE_TAP_HOME_RECENT_SYSTEM_UI); + sendKeyCombination(new int[]{KeyEvent.KEYCODE_HOME}, 0 /* duration */); + sendKeyCombination(new int[]{KeyEvent.KEYCODE_HOME}, 0 /* duration */); mPhoneWindowManager.assertKeyGestureCompleted( - new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType, - "Failed while executing " + testName); + new int[]{KeyEvent.KEYCODE_HOME}, /* modifierState = */0, + KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, + "Failed while executing Double tap HOME -> Open App switcher"); } @Test - @Parameters(method = "settingsKeyTestArguments") - public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior, - @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey, - int expectedModifierState) { - mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior); - testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey, - expectedModifierState); + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) + public void testSettingsKey_ToggleNotificationBehavior() { + mPhoneWindowManager.overrideSettingsKeyBehavior(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL); + testShortcutInternal("SETTINGS key -> Toggle Notification panel", + new int[]{KeyEvent.KEYCODE_SETTINGS}, + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + KeyEvent.KEYCODE_SETTINGS, 0); } @Test @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testBugreportShortcutPress() { testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL}, @@ -599,4 +558,145 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase { sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH)); mPhoneWindowManager.assertLaunchSearch(); } + + @Test + public void testKeyGestureScreenshotChord() { + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); + mPhoneWindowManager.moveTimeForward(500); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); + mPhoneWindowManager.assertTakeScreenshotCalled(); + } + + @Test + public void testKeyGestureScreenshotChordCancelled() { + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD)); + mPhoneWindowManager.assertTakeScreenshotNotCalled(); + } + + @Test + public void testKeyGestureAccessibilityShortcutChord() { + Assert.assertTrue( + sendKeyGestureEventStart( + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); + mPhoneWindowManager.moveTimeForward(5000); + Assert.assertTrue( + sendKeyGestureEventCancel( + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); + mPhoneWindowManager.assertAccessibilityKeychordCalled(); + } + + @Test + public void testKeyGestureAccessibilityShortcutChordCancelled() { + Assert.assertTrue( + sendKeyGestureEventStart( + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); + Assert.assertTrue( + sendKeyGestureEventCancel( + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD)); + mPhoneWindowManager.assertAccessibilityKeychordNotCalled(); + } + + @Test + public void testKeyGestureRingerToggleChord() { + mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE); + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); + mPhoneWindowManager.moveTimeForward(500); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); + mPhoneWindowManager.assertVolumeMute(); + } + + @Test + public void testKeyGestureRingerToggleChordCancelled() { + mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE); + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD)); + mPhoneWindowManager.assertVolumeNotMuted(); + } + + @Test + public void testKeyGestureGlobalAction() { + mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS); + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); + mPhoneWindowManager.moveTimeForward(500); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); + mPhoneWindowManager.assertShowGlobalActionsCalled(); + } + + @Test + public void testKeyGestureGlobalActionCancelled() { + mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS); + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS)); + mPhoneWindowManager.assertShowGlobalActionsNotCalled(); + } + + @Test + public void testKeyGestureAccessibilityTvShortcutChord() { + Assert.assertTrue( + sendKeyGestureEventStart( + KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); + mPhoneWindowManager.moveTimeForward(5000); + Assert.assertTrue( + sendKeyGestureEventCancel( + KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); + mPhoneWindowManager.assertAccessibilityKeychordCalled(); + } + + @Test + public void testKeyGestureAccessibilityTvShortcutChordCancelled() { + Assert.assertTrue( + sendKeyGestureEventStart( + KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); + Assert.assertTrue( + sendKeyGestureEventCancel( + KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD)); + mPhoneWindowManager.assertAccessibilityKeychordNotCalled(); + } + + @Test + public void testKeyGestureTvTriggerBugReport() { + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); + mPhoneWindowManager.moveTimeForward(1000); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); + mPhoneWindowManager.assertBugReportTakenForTv(); + } + + @Test + public void testKeyGestureTvTriggerBugReportCancelled() { + Assert.assertTrue( + sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); + Assert.assertTrue( + sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT)); + mPhoneWindowManager.assertBugReportNotTakenForTv(); + } + + @Test + public void testKeyGestureAccessibilityShortcut() { + Assert.assertTrue( + sendKeyGestureEventComplete( + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT)); + mPhoneWindowManager.assertAccessibilityKeychordCalled(); + } + + @Test + public void testKeyGestureCloseAllDialogs() { + Assert.assertTrue( + sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS)); + mPhoneWindowManager.assertCloseAllDialogs(); + } } diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java index 43171f8478183062ab459aa1e9f3a7db93ce95ff..c186a035558897dd2bb14696e26bd7d573273960 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java +++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java @@ -119,6 +119,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * ALT + TAB to show recent apps. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testAltTab() { mPhoneWindowManager.overrideStatusBarManagerInternal(); sendKeyCombination(new int[]{KEYCODE_ALT_LEFT, KEYCODE_TAB}, 0); @@ -129,6 +130,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * CTRL + SPACE to switch keyboard layout. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testCtrlSpace() { sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, /* duration= */ 0, ANY_DISPLAY_ID); @@ -139,6 +141,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * CTRL + SHIFT + SPACE to switch keyboard layout backwards. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testCtrlShiftSpace() { sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, /* duration= */ 0, ANY_DISPLAY_ID); @@ -149,6 +152,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * CTRL + ALT + Z to enable accessibility service. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testCtrlAltZ() { sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_ALT_LEFT, KEYCODE_Z}, 0); mPhoneWindowManager.assertAccessibilityKeychordCalled(); @@ -158,6 +162,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * META + CTRL+ S to take screenshot. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testMetaCtrlS() { sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_S}, 0); mPhoneWindowManager.assertTakeScreenshotCalled(); @@ -167,6 +172,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * META + N to expand notification panel. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testMetaN() throws RemoteException { mPhoneWindowManager.overrideTogglePanel(); sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_N}, 0); @@ -177,6 +183,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * META + SLASH to toggle shortcuts menu. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testMetaSlash() { mPhoneWindowManager.overrideStatusBarManagerInternal(); sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_SLASH}, 0); @@ -187,6 +194,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * META + ALT to toggle Cap Lock. */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testMetaAlt() { sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ALT_LEFT}, 0); mPhoneWindowManager.assertToggleCapsLock(); @@ -196,6 +204,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * META + H to go to homescreen */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testMetaH() { mPhoneWindowManager.overrideLaunchHome(); sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_H}, 0); @@ -206,6 +215,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * META + ENTER to go to homescreen */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testMetaEnter() { mPhoneWindowManager.overrideLaunchHome(); sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_ENTER}, 0); @@ -216,6 +226,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * Sends a KEYCODE_BRIGHTNESS_DOWN event and validates the brightness is decreased as expected; */ @Test + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testKeyCodeBrightnessDown() { float[] currentBrightness = new float[]{0.1f, 0.05f, 0.0f}; float[] newBrightness = new float[]{0.065738f, 0.0275134f, 0.0f}; @@ -231,9 +242,9 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled */ @Test + @EnableFlags(com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE) + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testTakeScreenshot_flagEnabled() { - mSetFlagsRule.enableFlags(com.android.hardware.input.Flags - .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE); sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0); mPhoneWindowManager.assertTakeScreenshotCalled(); } @@ -242,9 +253,9 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled */ @Test + @DisableFlags({com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE, + com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER}) public void testTakeScreenshot_flagDisabled() { - mSetFlagsRule.disableFlags(com.android.hardware.input.Flags - .FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE); sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0); mPhoneWindowManager.assertTakeScreenshotNotCalled(); } @@ -254,6 +265,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { */ @Test @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) + @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER) public void testTakeBugReport_flagEnabled() throws RemoteException { sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0); mPhoneWindowManager.assertTakeBugreport(true); @@ -263,7 +275,8 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase { * META+CTRL+BACKSPACE for taking a bugreport does nothing when the flag is disabledd. */ @Test - @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) + @DisableFlags({com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT, + com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER}) public void testTakeBugReport_flagDisabled() throws RemoteException { sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0); mPhoneWindowManager.assertTakeBugreport(false); diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java index 50b7db434267adfec5e95c1e19daaa2f3963ea67..9e47a008592c436f46d47ac40af877ac6ce994c6 100644 --- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java +++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java @@ -241,6 +241,13 @@ class ShortcutKeyTestBase { KeyGestureEvent.ACTION_GESTURE_COMPLETE).build()); } + boolean sendKeyGestureEventCancel(int gestureType) { + return mPhoneWindowManager.sendKeyGestureEvent( + new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction( + KeyGestureEvent.ACTION_GESTURE_COMPLETE).setFlags( + KeyGestureEvent.FLAG_CANCELLED).build()); + } + boolean sendKeyGestureEventComplete(int gestureType, int modifierState) { return mPhoneWindowManager.sendKeyGestureEvent( new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType( @@ -276,7 +283,7 @@ class ShortcutKeyTestBase { if ((actions & ACTION_PASS_TO_USER) != 0) { if (0 == mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent)) { if (!mDispatchedKeyHandler.onKeyDispatched(keyEvent)) { - mPhoneWindowManager.dispatchUnhandledKey(keyEvent); + mPhoneWindowManager.interceptUnhandledKey(keyEvent); } } } diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java index 98401b33840f42ab4aa8feaa7e7c6010fb3f43f7..1aa908792c0eb2560d61c3db369638d205203035 100644 --- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java +++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java @@ -418,8 +418,8 @@ class TestPhoneWindowManager { mKeyEventPolicyFlags); } - void dispatchUnhandledKey(KeyEvent event) { - mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE); + void interceptUnhandledKey(KeyEvent event) { + mPhoneWindowManager.interceptUnhandledKey(event, mInputToken); } boolean sendKeyGestureEvent(KeyGestureEvent event) { @@ -657,16 +657,36 @@ class TestPhoneWindowManager { verify(mPowerManager).userActivity(anyLong(), anyBoolean()); } + void assertShowGlobalActionsNotCalled() { + mTestLooper.dispatchAll(); + verify(mGlobalActions, never()).showDialog(anyBoolean(), anyBoolean()); + verify(mPowerManager, never()).userActivity(anyLong(), anyBoolean()); + } + void assertVolumeMute() { mTestLooper.dispatchAll(); verify(mAudioManagerInternal).silenceRingerModeInternal(eq("volume_hush")); } + void assertVolumeNotMuted() { + mTestLooper.dispatchAll(); + verify(mAudioManagerInternal, never()).silenceRingerModeInternal(any()); + } + void assertAccessibilityKeychordCalled() { mTestLooper.dispatchAll(); verify(mAccessibilityShortcutController).performAccessibilityShortcut(); } + void assertAccessibilityKeychordNotCalled() { + mTestLooper.dispatchAll(); + verify(mAccessibilityShortcutController, never()).performAccessibilityShortcut(); + } + + void assertCloseAllDialogs() { + verify(mContext).closeSystemDialogs(); + } + void assertDreamRequest() { mTestLooper.dispatchAll(); verify(mDreamManagerInternal).requestDream(); @@ -809,6 +829,16 @@ class TestPhoneWindowManager { } + void assertBugReportTakenForTv() { + mTestLooper.dispatchAll(); + verify(mPhoneWindowManager).requestBugreportForTv(); + } + + void assertBugReportNotTakenForTv() { + mTestLooper.dispatchAll(); + verify(mPhoneWindowManager, never()).requestBugreportForTv(); + } + void assertTogglePanel() throws RemoteException { mTestLooper.dispatchAll(); verify(mPhoneWindowManager.mStatusBarService).togglePanel(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java index 8227ed915c8e93a1c56223e95d30bd7c7b025112..92205f391f32141208b1ae9fcfd738e15cd5faf6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -230,6 +230,10 @@ class AppCompatActivityRobot { mDisplayContent.setIgnoreOrientationRequest(enabled); } + void setTopActivityOrganizedTask() { + doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask(); + } + void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) { doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java index d66c21a77fcdbf91817780a57c8649f47a1b3dbc..b91a5b7afe269877a6a3b1576ba22e5f1e929569 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java @@ -28,7 +28,7 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_V import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; +import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import android.compat.testing.PlatformCompatChangeRule; import android.platform.test.annotations.DisableFlags; @@ -218,7 +218,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { } @Test - @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() { runTestScenario((robot) -> { robot.activity().createActivityWithComponentInNewTask(); @@ -229,7 +229,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { @Test @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT}) - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() { runTestScenario((robot) -> { robot.activity().createActivityWithComponentInNewTask(); @@ -240,7 +240,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { @Test @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT}) - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse() { runTestScenario((robot) -> { robot.activity().createActivityWithComponentInNewTask(); @@ -250,7 +250,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() { runTestScenario((robot) -> { robot.activity().createActivityWithComponentInNewTask(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java index d91b38efd40bd275aebf79456f64e115610840b3..41102d6922daafc1499909f8b198dabbefee2b2d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java @@ -20,7 +20,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; +import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -85,7 +85,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraCompatFreeformPolicy_presentWhenEnabledAndDW() { runTestScenario((robot) -> { robot.allowEnterDesktopMode(/* isAllowed= */ true); @@ -95,7 +95,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraCompatFreeformPolicy_notPresentWhenNoDW() { runTestScenario((robot) -> { robot.allowEnterDesktopMode(/* isAllowed= */ false); @@ -105,7 +105,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraCompatFreeformPolicy_notPresentWhenNoFlag() { runTestScenario((robot) -> { robot.allowEnterDesktopMode(/* isAllowed= */ true); @@ -115,7 +115,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraCompatFreeformPolicy_notPresentWhenNoFlagAndNoDW() { runTestScenario((robot) -> { robot.allowEnterDesktopMode(/* isAllowed= */ false); @@ -125,7 +125,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraCompatFreeformPolicy_startedWhenEnabledAndDW() { runTestScenario((robot) -> { robot.allowEnterDesktopMode(/* isAllowed= */ true); @@ -136,7 +136,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraStateManager_existsWhenCameraCompatFreeformExists() { runTestScenario((robot) -> { robot.allowEnterDesktopMode(true); @@ -147,7 +147,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraStateManager_startedWhenCameraCompatFreeformExists() { runTestScenario((robot) -> { robot.allowEnterDesktopMode(true); @@ -180,7 +180,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase { } @Test - @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testCameraStateManager_doesNotExistWhenNoPolicyExists() { runTestScenario((robot) -> { robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ false); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java index 21fac9bcd1e4c10ff7fa93b4609b9f774ea09edd..d8373c5dc3d6296cc6b4f3dcf175a62413eaa960 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java @@ -16,10 +16,14 @@ package com.android.server.wm; +import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.mockito.Mockito.when; +import android.app.CameraCompatTaskInfo.FreeformCameraCompatMode; +import android.app.TaskInfo; import android.platform.test.annotations.Presubmit; import androidx.annotation.NonNull; @@ -114,6 +118,72 @@ public class AppCompatUtilsTest extends WindowTestsBase { }); } + @Test + public void testTopActivityEligibleForUserAspectRatioButton_eligible() { + runTestScenario((robot) -> { + robot.applyOnActivity((a) -> { + a.createActivityWithComponentInNewTask(); + a.setIgnoreOrientationRequest(true); + }); + robot.conf().enableUserAppAspectRatioSettings(true); + + robot.checkTaskInfoEligibleForUserAspectRatioButton(true); + }); + } + + @Test + public void testTopActivityEligibleForUserAspectRatioButton_disabled_notEligible() { + runTestScenario((robot) -> { + robot.applyOnActivity((a) -> { + a.createActivityWithComponentInNewTask(); + a.setIgnoreOrientationRequest(true); + }); + robot.conf().enableUserAppAspectRatioSettings(false); + + robot.checkTaskInfoEligibleForUserAspectRatioButton(false); + }); + } + + @Test + public void testTopActivityEligibleForUserAspectRatioButton_inSizeCompatMode_notEligible() { + runTestScenario((robot) -> { + robot.applyOnActivity((a) -> { + a.createActivityWithComponentInNewTask(); + a.setIgnoreOrientationRequest(true); + a.setTopActivityOrganizedTask(); + a.setTopActivityInSizeCompatMode(true); + a.setTopActivityVisible(true); + }); + robot.conf().enableUserAppAspectRatioSettings(true); + + robot.checkTaskInfoEligibleForUserAspectRatioButton(false); + }); + } + + @Test + public void testTopActivityEligibleForUserAspectRatioButton_transparentTop_notEligible() { + runTestScenario((robot) -> { + robot.transparentActivity((ta) -> { + ta.launchTransparentActivityInTask(); + ta.activity().setIgnoreOrientationRequest(true); + }); + robot.conf().enableUserAppAspectRatioSettings(true); + + robot.checkTaskInfoEligibleForUserAspectRatioButton(false); + }); + } + + @Test + public void getTaskInfoPropagatesCameraCompatMode() { + runTestScenario((robot) -> { + robot.applyOnActivity(AppCompatActivityRobot::createActivityWithComponentInNewTask); + + robot.setFreeformCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); + robot.checkTaskInfoFreeformCameraCompatMode( + CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); + }); + } + /** * Runs a test scenario providing a Robot. */ @@ -125,11 +195,14 @@ public class AppCompatUtilsTest extends WindowTestsBase { private static class AppCompatUtilsRobotTest extends AppCompatRobotBase { private final WindowState mWindowState; + @NonNull + private final AppCompatTransparentActivityRobot mTransparentActivityRobot; AppCompatUtilsRobotTest(@NonNull WindowManagerService wm, @NonNull ActivityTaskManagerService atm, @NonNull ActivityTaskSupervisor supervisor) { super(wm, atm, supervisor); + mTransparentActivityRobot = new AppCompatTransparentActivityRobot(activity()); mWindowState = Mockito.mock(WindowState.class); } @@ -139,6 +212,12 @@ public class AppCompatUtilsTest extends WindowTestsBase { spyOn(activity.mAppCompatController.getAppCompatAspectRatioPolicy()); } + void transparentActivity(@NonNull Consumer consumer) { + // We always create at least an opaque activity in a Task. + activity().createNewTaskWithBaseActivity(); + consumer.accept(mTransparentActivityRobot); + } + void setIsLetterboxedForFixedOrientationAndAspectRatio( boolean forFixedOrientationAndAspectRatio) { when(activity().top().mAppCompatController.getAppCompatAspectRatioPolicy() @@ -155,11 +234,30 @@ public class AppCompatUtilsTest extends WindowTestsBase { when(mWindowState.isLetterboxedForDisplayCutout()).thenReturn(displayCutout); } + void setFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) { + activity().top().mAppCompatController.getAppCompatCameraOverrides() + .setFreeformCameraCompatMode(mode); + } + void checkTopActivityLetterboxReason(@NonNull String expected) { Assert.assertEquals(expected, AppCompatUtils.getLetterboxReasonString(activity().top(), mWindowState)); } + @NonNull + TaskInfo getTopTaskInfo() { + return activity().top().getTask().getTaskInfo(); + } + + void checkTaskInfoEligibleForUserAspectRatioButton(boolean eligible) { + Assert.assertEquals(eligible, getTopTaskInfo().appCompatTaskInfo + .eligibleForUserAspectRatioButton()); + } + + void checkTaskInfoFreeformCameraCompatMode(@FreeformCameraCompatMode int mode) { + Assert.assertEquals(mode, getTopTaskInfo().appCompatTaskInfo + .cameraCompatTaskInfo.freeformCameraCompatMode); + } } } diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java index a48813d775d15b0564ddff5fc6059c75bf6843ae..dbcef10a6be233e032a0bd7f4e8f3f4175dd5e4c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java @@ -35,7 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; -import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; +import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -60,6 +60,7 @@ import android.content.res.Configuration.Orientation; import android.graphics.Rect; import android.hardware.camera2.CameraManager; import android.os.Handler; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.Surface; @@ -135,7 +136,6 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { }); mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler); - mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM); CameraStateMonitor cameraStateMonitor = new CameraStateMonitor(mDisplayContent, mMockHandler); mCameraCompatFreeformPolicy = @@ -147,6 +147,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { cameraStateMonitor.startListeningToCameraState(); } + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test public void testFullscreen_doesNotActivateCameraCompatMode() { configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN); @@ -157,6 +158,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { assertNotInCameraCompatMode(); } + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test public void testOrientationUnspecified_doesNotActivateCameraCompatMode() { configureActivity(SCREEN_ORIENTATION_UNSPECIFIED); @@ -164,12 +166,14 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { assertNotInCameraCompatMode(); } + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test public void testNoCameraConnection_doesNotActivateCameraCompatMode() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); assertNotInCameraCompatMode(); } + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -210,6 +214,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { assertActivityRefreshRequested(/* refreshRequested */ false); } + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -235,6 +240,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT}) public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -246,6 +252,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -254,6 +261,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); @@ -268,6 +276,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception { when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) .thenReturn(false); @@ -281,6 +290,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase { } @Test + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 85cb1bcc01fb0480fb80e740df403c96aa9492cc..5c0d424f4f42de94cb3a26d92dddbdfb59e5bddb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -83,7 +83,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFO import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; -import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM; +import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING; import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE; import static com.google.common.truth.Truth.assertThat; @@ -2820,7 +2820,7 @@ public class DisplayContentTests extends WindowTestsBase { verify(mWm.mUmInternal, never()).isUserVisible(userId2, displayId); } - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test public void cameraCompatFreeformFlagEnabled_cameraCompatFreeformPolicyNotNull() { doReturn(true).when(() -> @@ -2829,7 +2829,7 @@ public class DisplayContentTests extends WindowTestsBase { assertTrue(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()); } - @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @Test public void cameraCompatFreeformFlagNotEnabled_cameraCompatFreeformPolicyIsNull() { doReturn(true).when(() -> @@ -2838,7 +2838,7 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy()); } - @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM) + @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING) @DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE) @Test public void desktopWindowingFlagNotEnabled_cameraCompatFreeformPolicyIsNull() { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index f32a234f3e402bd8c05e5ca15a167ea0e8ba81e5..6a89178ec9bfb7485fb650901f3e673d422d26fa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -413,15 +413,25 @@ public class DisplayPolicyTests extends WindowTestsBase { @Test public void testUpdateDisplayConfigurationByDecor() { - if (Flags.insetsDecoupledConfiguration()) { - // No configuration update when flag enables. - return; - } doReturn(NO_CUTOUT).when(mDisplayContent).calculateDisplayCutoutForRotation(anyInt()); final WindowState navbar = createNavBarWithProvidedInsets(mDisplayContent); final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); final DisplayInfo di = mDisplayContent.getDisplayInfo(); final int prevScreenHeightDp = mDisplayContent.getConfiguration().screenHeightDp; + if (Flags.insetsDecoupledConfiguration()) { + // No configuration update when flag enables. + assertFalse(displayPolicy.updateDecorInsetsInfo()); + assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation, + di.logicalHeight, di.logicalWidth).mOverrideConfigInsets.bottom); + + final int barHeight = 2 * NAV_BAR_HEIGHT; + navbar.mAttrs.providedInsets[0].setInsetsSize(Insets.of(0, 0, 0, barHeight)); + assertFalse(displayPolicy.updateDecorInsetsInfo()); + assertEquals(barHeight, displayPolicy.getDecorInsetsInfo(di.rotation, + di.logicalHeight, di.logicalWidth).mOverrideConfigInsets.bottom); + return; + } + assertTrue(navbar.providesDisplayDecorInsets() && displayPolicy.updateDecorInsetsInfo()); assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth, di.logicalHeight).mConfigInsets.bottom); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index e8d089c61362c715caf39cd45ad9801284873d21..457058849fca6249b244672cb8c08a11744abbd4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -188,7 +188,7 @@ public class InsetsSourceProviderTest extends WindowTestsBase { assertNull(mProvider.getLeash(target)); // Set the leash to be ready for dispatching. - mProvider.mIsLeashReadyForDispatching = true; + mProvider.mIsLeashInitialized = true; assertNotNull(mProvider.getLeash(target)); // We do have fake control for the fake control target, but that has no leash. diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 1be61c36f272d0c9c7be51bb7f2edcd3fb421d6a..66d7963946b9c8cefd8f705d55ccc0fc2510cc08 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -494,6 +494,21 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { assertTrue("Result should be empty.", mResult.isEmpty()); } + @Test + public void testAbortsLoadingWhenUserCleansUpBeforeLoadingFinishes() { + mTarget.saveTask(mTestTask); + mPersisterQueue.flush(); + + final LaunchParamsPersister target = new LaunchParamsPersister(mPersisterQueue, mSupervisor, + mUserFolderGetter); + target.onSystemReady(); + target.onUnlockUser(TEST_USER_ID); + target.onCleanupUser(TEST_USER_ID); + + target.getLaunchParams(mTestTask, null, mResult); + assertTrue("Result should be empty.", mResult.isEmpty()); + } + private static boolean deleteRecursively(File file) { boolean result = true; if (file.isDirectory()) { diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 8f3d3c5a86e9619c832bc47ca2c40bf8c517d07b..df17cd1d24b7c98693ccd5430c46eed3799eb0df 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -70,7 +70,10 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import android.util.IntArray; import android.util.SparseBooleanArray; @@ -79,9 +82,12 @@ import android.window.TaskSnapshot; import androidx.test.filters.MediumTest; +import com.android.launcher3.Flags; import com.android.server.wm.RecentTasks.Callbacks; import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -122,6 +128,10 @@ public class RecentTasksTest extends WindowTestsBase { private CallbacksRecorder mCallbacksRecorder; + @Rule + public SetFlagsRule mSetFlagsRule = + new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + @Before public void setUp() throws Exception { mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); @@ -421,6 +431,29 @@ public class RecentTasksTest extends WindowTestsBase { assertThat(mCallbacksRecorder.mRemoved).contains(task1); } + @Test + public void testAddTaskCompatibleWindowingMode_withFreeformAndFullscreen_expectRemove() { + Task task1 = createTaskBuilder(".Task1") + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .build(); + doReturn(WINDOWING_MODE_FREEFORM).when(task1).getWindowingMode(); + mRecentTasks.add(task1); + mCallbacksRecorder.clear(); + + Task task2 = createTaskBuilder(".Task1") + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setFlags(FLAG_ACTIVITY_NEW_TASK) + .build(); + assertEquals(WINDOWING_MODE_FULLSCREEN, task2.getWindowingMode()); + mRecentTasks.add(task2); + + assertThat(mCallbacksRecorder.mAdded).hasSize(1); + assertThat(mCallbacksRecorder.mAdded).contains(task2); + assertThat(mCallbacksRecorder.mTrimmed).isEmpty(); + assertThat(mCallbacksRecorder.mRemoved).hasSize(1); + assertThat(mCallbacksRecorder.mRemoved).contains(task1); + } + @Test public void testAddTaskIncompatibleWindowingMode_expectNoRemove() { Task task1 = createTaskBuilder(".Task1") @@ -697,14 +730,31 @@ public class RecentTasksTest extends WindowTestsBase { } @Test + @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) public void testVisibleTasks_excludedFromRecents() { + testVisibleTasks_excludedFromRecents_internal(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + public void testVisibleTasks_excludedFromRecents_withRefactorFlag() { + testVisibleTasks_excludedFromRecents_internal(); + } + + private void testVisibleTasks_excludedFromRecents_internal() { mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); - Task excludedTask1 = createTaskBuilder(".ExcludedTask1") + Task invisibleExcludedTask = createTaskBuilder(".ExcludedTask1") .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .setCreateActivity(true) .build(); - Task excludedTask2 = createTaskBuilder(".ExcludedTask2") + ActivityRecord activityRecord = invisibleExcludedTask.getTopMostActivity(); + activityRecord.setVisibleRequested(false); + activityRecord.setVisible(false); + + Task visibleExcludedTask = createTaskBuilder(".ExcludedTask2") .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .setCreateActivity(true) .build(); Task detachedExcludedTask = createTaskBuilder(".DetachedExcludedTask") .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) @@ -718,18 +768,79 @@ public class RecentTasksTest extends WindowTestsBase { assertFalse(detachedExcludedTask.isAttached()); mRecentTasks.add(detachedExcludedTask); - mRecentTasks.add(excludedTask1); + mRecentTasks.add(invisibleExcludedTask); mRecentTasks.add(mTasks.get(0)); mRecentTasks.add(mTasks.get(1)); mRecentTasks.add(mTasks.get(2)); - mRecentTasks.add(excludedTask2); + mRecentTasks.add(visibleExcludedTask); + + // Excluded tasks should be trimmed, except those with a visible activity. + triggerTrimAndAssertTrimmed(invisibleExcludedTask, detachedExcludedTask); + } + + @Test + @Ignore("b/342627272") + @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask() { + testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); + } - // Except the first-most excluded task, other excluded tasks should be trimmed. - triggerTrimAndAssertTrimmed(excludedTask1, detachedExcludedTask); + @Test + @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + public void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_withRefactorFlag() { + testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal(); + } + + private void testVisibleTasks_excludedFromRecents_visibleTaskNotFirstTask_internal() { + mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); + + Task invisibleExcludedTask = createTaskBuilder(".ExcludedTask1") + .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .setCreateActivity(true) + .build(); + ActivityRecord activityRecord = invisibleExcludedTask.getTopMostActivity(); + activityRecord.setVisibleRequested(false); + activityRecord.setVisible(false); + + Task visibleExcludedTask = createTaskBuilder(".ExcludedTask2") + .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .setCreateActivity(true) + .build(); + Task detachedExcludedTask = createTaskBuilder(".DetachedExcludedTask") + .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .build(); + + // Move home to front so other task can satisfy the condition in RecentTasks#isTrimmable. + mRootWindowContainer.getDefaultTaskDisplayArea().getRootHomeTask().moveToFront("test"); + // Avoid Task#autoRemoveFromRecents when removing from parent. + detachedExcludedTask.setHasBeenVisible(true); + detachedExcludedTask.removeImmediately(); + assertFalse(detachedExcludedTask.isAttached()); + + mRecentTasks.add(detachedExcludedTask); + mRecentTasks.add(visibleExcludedTask); + mRecentTasks.add(mTasks.get(0)); + mRecentTasks.add(mTasks.get(1)); + mRecentTasks.add(mTasks.get(2)); + mRecentTasks.add(invisibleExcludedTask); + + // Excluded tasks should be trimmed, except those with a visible activity. + triggerTrimAndAssertTrimmed(invisibleExcludedTask, detachedExcludedTask); } @Test + @DisableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() { + testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL) + public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_withRefactorFlag() { + testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal(); + } + + private void testVisibleTasks_excludedFromRecents_firstTaskNotVisible_internal() { // Create some set of tasks, some of which are visible and some are not Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask") .setParentTask(mTaskContainer.getRootHomeTask()) @@ -738,11 +849,12 @@ public class RecentTasksTest extends WindowTestsBase { mRecentTasks.add(homeTask); Task excludedTask1 = createTaskBuilder(".ExcludedTask1") .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .setCreateActivity(true) .build(); excludedTask1.mUserSetupComplete = true; mRecentTasks.add(excludedTask1); - // Expect that the first visible excluded-from-recents task is visible + // Expect that the visible excluded-from-recents task is visible assertGetRecentTasksOrder(0 /* flags */, excludedTask1); } @@ -1439,9 +1551,9 @@ public class RecentTasksTest extends WindowTestsBase { */ private void assertGetRecentTasksOrder(int getRecentTaskFlags, Task... expectedTasks) { List infos = getRecentTasks(getRecentTaskFlags); - assertTrue(expectedTasks.length == infos.size()); - for (int i = 0; i < infos.size(); i++) { - assertTrue(expectedTasks[i].mTaskId == infos.get(i).taskId); + assertEquals(expectedTasks.length, infos.size()); + for (int i = 0; i < infos.size(); i++) { + assertEquals(expectedTasks[i].mTaskId, infos.get(i).taskId); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index ae0c6e5512465bae857574c1e5fcd90a368894aa..cf1dcd0515d1d36f236c13b6da691f829c408b75 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -331,6 +331,8 @@ public class RootWindowContainerTests extends WindowTestsBase { final WindowProcessController proc = mSystemServicesTestRule.addProcess( activity.packageName, activity.processName, 6789 /* pid */, activity.info.applicationInfo.uid); + mAtm.mInternal.preBindApplication(proc, proc.mInfo); + assertTrue(proc.registeredForActivityConfigChanges()); assertFalse(proc.mHasEverAttached); try { mRootWindowContainer.attachApplication(proc); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 7bce8285972cc23bca7fbb720df0714214bd439a..8fa4667c3b243ce7571dd979b0650f71c707307e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -4063,8 +4063,9 @@ public class SizeCompatTests extends WindowTestsBase { .setInsetsSize(Insets.of(0, 0, 0, 150)) }; display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs); - assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo()); - display.sendNewConfiguration(); + if (display.getDisplayPolicy().updateDecorInsetsInfo()) { + display.sendNewConfiguration(); + } final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) @@ -4097,8 +4098,9 @@ public class SizeCompatTests extends WindowTestsBase { .setInsetsSize(Insets.of(0, 0, 0, 150)) }; display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs); - assertTrue(display.getDisplayPolicy().updateDecorInsetsInfo()); - display.sendNewConfiguration(); + if (display.getDisplayPolicy().updateDecorInsetsInfo()) { + display.sendNewConfiguration(); + } final ActivityRecord activity = getActivityBuilderOnSameTask() .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) @@ -4126,8 +4128,9 @@ public class SizeCompatTests extends WindowTestsBase { .setInsetsSize(Insets.of(0, 0, 0, 150)) }; dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs); - assertTrue(dc.getDisplayPolicy().updateDecorInsetsInfo()); - dc.sendNewConfiguration(); + if (dc.getDisplayPolicy().updateDecorInsetsInfo()) { + dc.sendNewConfiguration(); + } final ActivityRecord activity = getActivityBuilderOnSameTask() .setResizeMode(RESIZE_MODE_UNRESIZEABLE) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 4b03483d43b9199541045b167dca101f6a140795..e4512c31069a4be99b592c29de434837004c26dd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -18,7 +18,6 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -704,50 +703,6 @@ public class TaskTests extends WindowTestsBase { assertEquals(freeformBounds, task.getBounds()); } - @Test - public void testTopActivityEligibleForUserAspectRatioButton() { - DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay(); - final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) - .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); - final Task task = rootTask.getBottomMostTask(); - final ActivityRecord root = task.getTopNonFinishingActivity(); - spyOn(mWm.mAppCompatConfiguration); - spyOn(root); - spyOn(root.mAppCompatController.getAppCompatAspectRatioOverrides()); - - doReturn(true).when(root).fillsParent(); - doReturn(true).when( - root.mAppCompatController.getAppCompatAspectRatioOverrides()) - .shouldEnableUserAspectRatioSettings(); - doReturn(false).when(root).inSizeCompatMode(); - doReturn(task).when(root).getOrganizedTask(); - - // The button should be eligible to be displayed - assertTrue(task.getTaskInfo() - .appCompatTaskInfo.eligibleForUserAspectRatioButton()); - - // When shouldApplyUserMinAspectRatioOverride is disable the button is not enabled - doReturn(false).when( - root.mAppCompatController.getAppCompatAspectRatioOverrides()) - .shouldEnableUserAspectRatioSettings(); - assertFalse(task.getTaskInfo() - .appCompatTaskInfo.eligibleForUserAspectRatioButton()); - doReturn(true).when(root.mAppCompatController - .getAppCompatAspectRatioOverrides()).shouldEnableUserAspectRatioSettings(); - - // When in size compat mode the button is not enabled - doReturn(true).when(root).inSizeCompatMode(); - assertFalse(task.getTaskInfo() - .appCompatTaskInfo.eligibleForUserAspectRatioButton()); - doReturn(false).when(root).inSizeCompatMode(); - - // When the top activity is transparent, the button is not enabled - doReturn(false).when(root).fillsParent(); - assertFalse(task.getTaskInfo() - .appCompatTaskInfo.eligibleForUserAspectRatioButton()); - doReturn(true).when(root).fillsParent(); - } - @Test public void testIsTopActivityTranslucent() { DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay(); @@ -2111,17 +2066,6 @@ public class TaskTests extends WindowTestsBase { assertNotEquals(activityDifferentPackage, task.getBottomMostActivityInSamePackage()); } - @Test - public void getTaskInfoPropagatesCameraCompatMode() { - final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).build(); - final ActivityRecord activity = task.getTopMostActivity(); - activity.mAppCompatController.getAppCompatCameraOverrides().setFreeformCameraCompatMode( - CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE); - - assertEquals(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE, - task.getTaskInfo().appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode); - } - @Test public void testUpdateTaskDescriptionOnReparent() { final Task rootTask1 = createTask(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index eebb487d16cd4e19249ebf33c269674ccc299bc7..9e9874b32893431c48517697613371ef9135415f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -103,8 +103,8 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public KeyEvent dispatchUnhandledKey(IBinder focusedToken, KeyEvent event, int policyFlags) { - return null; + public boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) { + return false; } @Override diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 6c1e1a428fb86cf4f626fc89634dcd45edf1db2e..129494517cd640a2fdd15b43fa43ed54dc546c50 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -1027,28 +1027,36 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0; if (enabled != mMidiEnabled) { if (enabled) { + boolean midiDeviceFound = false; if (android.hardware.usb.flags.Flags.enableUsbSysfsMidiIdentification()) { try { getMidiCardDevice(); + midiDeviceFound = true; } catch (FileNotFoundException e) { - Slog.e(TAG, "could not identify MIDI device", e); - enabled = false; + Slog.w(TAG, "could not identify MIDI device", e); } - } else { + } + // For backward compatibility with older kernels without + // https://lore.kernel.org/r/20240307030922.3573161-1-royluo@google.com + if (!midiDeviceFound) { Scanner scanner = null; try { scanner = new Scanner(new File(MIDI_ALSA_PATH)); mMidiCard = scanner.nextInt(); mMidiDevice = scanner.nextInt(); + midiDeviceFound = true; } catch (FileNotFoundException e) { Slog.e(TAG, "could not open MIDI file", e); - enabled = false; } finally { if (scanner != null) { scanner.close(); } } } + if (!midiDeviceFound) { + Slog.e(TAG, "Failed to enable MIDI function"); + enabled = false; + } } mMidiEnabled = enabled; } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index dc5f6e960a2b8be151351a76750eb9dbfe971cdd..fb031bd01673994377fb3015ae94d749934feba3 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -842,7 +842,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return; } - model.setRequested(config.allowMultipleTriggers); + model.setRequested(config.isAllowMultipleTriggers()); // TODO: Remove this block if the lower layer supports multiple triggers. if (model.isRequested()) { updateRecognitionLocked(model, true); @@ -964,7 +964,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { RecognitionConfig config = modelData.getRecognitionConfig(); if (config != null) { // Whether we should continue by starting this again. - modelData.setRequested(config.allowMultipleTriggers); + modelData.setRequested(config.isAllowMultipleTriggers()); } // TODO: Remove this block if the lower layer supports multiple triggers. if (modelData.isRequested()) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 862aff9be9ce3fa0039c2acdbedd6522965540cf..2bb86bc305a7d55cb21bee00c032c0c9fd357da9 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1439,7 +1439,7 @@ public class SoundTriggerService extends SystemService { runOrAddOperation(new Operation( // always execute: () -> { - if (!mRecognitionConfig.allowMultipleTriggers) { + if (!mRecognitionConfig.isAllowMultipleTriggers()) { // Unregister this remoteService once op is done synchronized (mCallbacksLock) { mCallbacks.remove(mPuuid.getUuid()); diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java index a6781478a7658bd53c7282e4ec7ef86d13bd12a4..0f4809c2918d1d3ddfda8e20e005103d262dc380 100644 --- a/telephony/common/android/telephony/LocationAccessPolicy.java +++ b/telephony/common/android/telephony/LocationAccessPolicy.java @@ -33,6 +33,7 @@ import android.util.Log; import android.widget.Toast; import com.android.internal.telephony.TelephonyPermissions; +import com.android.internal.telephony.flags.Flags; import com.android.internal.telephony.util.TelephonyUtils; /** @@ -283,6 +284,8 @@ public final class LocationAccessPolicy { int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) ? query.minSdkVersionForFine : query.minSdkVersionForCoarse; + UserHandle callingUserHandle = UserHandle.getUserHandleForUid(query.callingUid); + // If the app fails for some reason, see if it should be allowed to proceed. if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) { String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog @@ -291,7 +294,8 @@ public final class LocationAccessPolicy { + query.method; logError(context, query, errorMsg); return null; - } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) { + } else if (!isAppAtLeastSdkVersion(context, callingUserHandle, query.callingPackage, + minSdkVersion)) { String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog + " because it doesn't target API " + minSdkVersion + " yet." + " Please fix this app. Called from " + query.method; @@ -420,11 +424,19 @@ public final class LocationAccessPolicy { } } - private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) { + private static boolean isAppAtLeastSdkVersion(Context context, + @NonNull UserHandle callingUserHandle, String pkgName, int sdkVersion) { try { - if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion - >= sdkVersion) { - return true; + if (Flags.hsumPackageManager()) { + if (context.getPackageManager().getApplicationInfoAsUser( + pkgName, 0, callingUserHandle).targetSdkVersion >= sdkVersion) { + return true; + } + } else { + if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion + >= sdkVersion) { + return true; + } } } catch (PackageManager.NameNotFoundException e) { // In case of exception, assume known app (more strict checking) diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 2ef05735003361ebe17c87320c1f5b2714da2263..47f6764dba980af43bade1a17342ece65f53d33b 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9763,7 +9763,7 @@ public class CarrierConfigManager { * users to switch to using satellite emergency messaging. *

          *

          - * The default value is 300 seconds. + * The default value is 180 seconds. */ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = @@ -11257,7 +11257,7 @@ public class CarrierConfigManager { KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE, PersistableBundle.EMPTY); sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false); - sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 300); + sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 180); sDefaults.putIntArray(KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY, // Boundaries: [-140 dBm, -44 dBm] new int[]{ diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 3e226ccf2737b1991cb073cbbc0b0ce197c5d2b1..92effe05882a60f76e1b9ac54e22a61d98c1d7f2 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -19426,4 +19426,48 @@ public class TelephonyManager { return "UNKNOWN(" + state + ")"; } } + + /** + * This API can be used by only CTS to override the Euicc UI component. + * + * @param componentName ui component to be launched for testing. {@code null} to reset. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setTestEuiccUiComponent(@Nullable ComponentName componentName) { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + Rlog.e(TAG, "setTestEuiccUiComponent(): ITelephony instance is NULL"); + throw new IllegalStateException("Telephony service not available."); + } + telephony.setTestEuiccUiComponent(componentName); + } catch (RemoteException ex) { + Rlog.e(TAG, "setTestEuiccUiComponent() RemoteException : " + ex); + throw ex.rethrowAsRuntimeException(); + } + } + + /** + * This API can be used by only CTS to retrieve the Euicc UI component. + * + * @return The Euicc UI component for testing. {@code null} if not available. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @Nullable + public ComponentName getTestEuiccUiComponent() { + try { + ITelephony telephony = getITelephony(); + if (telephony == null) { + Rlog.e(TAG, "getTestEuiccUiComponent(): ITelephony instance is NULL"); + throw new IllegalStateException("Telephony service not available."); + } + return telephony.getTestEuiccUiComponent(); + } catch (RemoteException ex) { + Rlog.e(TAG, "getTestEuiccUiComponent() RemoteException : " + ex); + throw ex.rethrowAsRuntimeException(); + } + } } diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 44d3fca6aec62eccfdb25b85d95f2be48843046d..567314beadd35e4098147c015b51dfe9a1fddb2f 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -128,6 +128,12 @@ public class ApnSetting implements Parcelable { /** APN type for RCS (Rich Communication Services). */ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) public static final int TYPE_RCS = ApnTypes.RCS; + /** APN type for OEM_PAID networks (Automotive PANS) */ + @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE) + public static final int TYPE_OEM_PAID = 1 << 16; // TODO(b/366194627): ApnTypes.OEM_PAID; + /** APN type for OEM_PRIVATE networks (Automotive PANS) */ + @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE) + public static final int TYPE_OEM_PRIVATE = 1 << 17; // TODO(b/366194627): ApnTypes.OEM_PRIVATE; /** @hide */ @IntDef(flag = true, prefix = {"TYPE_"}, value = { @@ -146,7 +152,9 @@ public class ApnSetting implements Parcelable { TYPE_BIP, TYPE_VSIM, TYPE_ENTERPRISE, - TYPE_RCS + TYPE_RCS, + TYPE_OEM_PAID, + TYPE_OEM_PRIVATE, }) @Retention(RetentionPolicy.SOURCE) public @interface ApnType { @@ -375,6 +383,27 @@ public class ApnSetting implements Parcelable { @SystemApi public static final String TYPE_RCS_STRING = "rcs"; + /** + * APN type for OEM_PAID networks (Automotive PANS) + * + * Note: String representations of APN types are intended for system apps to communicate with + * modem components or carriers. Non-system apps should use the integer variants instead. + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE) + @SystemApi + public static final String TYPE_OEM_PAID_STRING = "oem_paid"; + + /** + * APN type for OEM_PRIVATE networks (Automotive PANS) + * + * Note: String representations of APN types are intended for system apps to communicate with + * modem components or carriers. Non-system apps should use the integer variants instead. + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE) + @SystemApi + public static final String TYPE_OEM_PRIVATE_STRING = "oem_private"; /** @hide */ @IntDef(prefix = { "AUTH_TYPE_" }, value = { @@ -489,6 +518,8 @@ public class ApnSetting implements Parcelable { APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM); APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP); APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS); + APN_TYPE_STRING_MAP.put(TYPE_OEM_PAID_STRING, TYPE_OEM_PAID); + APN_TYPE_STRING_MAP.put(TYPE_OEM_PRIVATE_STRING, TYPE_OEM_PRIVATE); APN_TYPE_INT_MAP = new ArrayMap<>(); APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING); @@ -507,6 +538,8 @@ public class ApnSetting implements Parcelable { APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING); APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING); APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING); + APN_TYPE_INT_MAP.put(TYPE_OEM_PAID, TYPE_OEM_PAID_STRING); + APN_TYPE_INT_MAP.put(TYPE_OEM_PRIVATE, TYPE_OEM_PRIVATE_STRING); PROTOCOL_STRING_MAP = new ArrayMap<>(); PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP); @@ -2383,7 +2416,8 @@ public class ApnSetting implements Parcelable { public ApnSetting build() { if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX - | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0 + | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS | TYPE_OEM_PAID + | TYPE_OEM_PRIVATE)) == 0 || TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) { return null; } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 4eefaaca71f4662b9759d075ced65fdaebf6d1c2..bd5c7597ba14110d7f0f488ffbf4cf5547828b64 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -1113,6 +1113,12 @@ public final class SatelliteManager { * @hide */ public static final int DATAGRAM_TYPE_SMS = 6; + /** + * Datagram type indicating that the message to be sent is an SMS checking + * for pending incoming SMS. + * @hide + */ + public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7; /** @hide */ @IntDef(prefix = "DATAGRAM_TYPE_", value = { @@ -1122,7 +1128,8 @@ public final class SatelliteManager { DATAGRAM_TYPE_KEEP_ALIVE, DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP, DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED, - DATAGRAM_TYPE_SMS + DATAGRAM_TYPE_SMS, + DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS }) @Retention(RetentionPolicy.SOURCE) public @interface DatagramType {} diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index e57c207a0b3e0a6a3207c48ff6dcefbd1f0ca2d2..cca0f8c4bc7065f5e6d3e55a22ae362a713a24ba 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -3409,4 +3409,20 @@ interface ITelephony { * @hide */ boolean setSatelliteSubscriberIdListChangedIntentComponent(in String name); + + /** + * This API can be used by only CTS to override the Euicc UI component. + * + * @param componentName ui component to be launched for testing + * @hide + */ + void setTestEuiccUiComponent(in ComponentName componentName); + + /** + * This API can be used by only CTS to retrieve the Euicc UI component. + * + * @return The Euicc UI component for testing. + * @hide + */ + ComponentName getTestEuiccUiComponent(); } diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..981b3168cdbb349371c1e584f74fceeeb32e3a72 --- /dev/null +++ b/tests/FlickerTests/ActivityEmbedding/OWNERS @@ -0,0 +1 @@ +# Bug component: 1168918 diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index ba360070abc3830a28fec2cdf122652f33f0e9a1..4ae06a4f9812779487fdf5609f18acb7c0098731 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -18,6 +18,8 @@ package com.android.server.input import android.content.Context import android.content.ContextWrapper +import android.content.pm.PackageManager +import android.content.res.Resources import android.hardware.input.IInputManager import android.hardware.input.AidlKeyGestureEvent import android.hardware.input.IKeyGestureEventListener @@ -25,15 +27,20 @@ import android.hardware.input.IKeyGestureHandler import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyGestureEvent -import android.hardware.input.KeyGestureEvent.KeyGestureType import android.os.IBinder import android.os.Process +import android.os.SystemClock +import android.os.SystemProperties import android.os.test.TestLooper +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit +import android.platform.test.flag.junit.SetFlagsRule import android.view.InputDevice -import android.view.KeyCharacterMap import android.view.KeyEvent +import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE import androidx.test.core.app.ApplicationProvider +import com.android.internal.R import com.android.internal.annotations.Keep import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule @@ -78,32 +85,60 @@ class KeyGestureControllerTests { KeyEvent.KEYCODE_META_LEFT to (KeyEvent.META_META_LEFT_ON or KeyEvent.META_META_ON), KeyEvent.KEYCODE_META_RIGHT to (KeyEvent.META_META_RIGHT_ON or KeyEvent.META_META_ON), ) + const val SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH = 0 + const val SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY = 1 + const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0 + const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1 + const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2 } @JvmField @Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this) - .mockStatic(FrameworkStatsLog::class.java).build()!! + .mockStatic(FrameworkStatsLog::class.java) + .mockStatic(SystemProperties::class.java).build()!! + + @JvmField + @Rule + val rule = SetFlagsRule() @Mock private lateinit var iInputManager: IInputManager + @Mock + private lateinit var resources: Resources + + @Mock + private lateinit var packageManager: PackageManager + private var currentPid = 0 - private lateinit var keyGestureController: KeyGestureController private lateinit var context: Context private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession private lateinit var testLooper: TestLooper private var events = mutableListOf() - private var handleEvents = mutableListOf() @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) + Mockito.`when`(context.resources).thenReturn(resources) inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) setupInputDevices() + setupBehaviors() testLooper = TestLooper() currentPid = Process.myPid() - keyGestureController = KeyGestureController(context, testLooper.looper) + } + + private fun setupBehaviors() { + Mockito.`when`( + resources.getBoolean( + com.android.internal.R.bool.config_enableScreenshotChord + ) + ).thenReturn(true) + Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) + .thenReturn(true) + Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) + .thenReturn(true) + Mockito.`when`(context.packageManager).thenReturn(packageManager) } private fun setupInputDevices() { @@ -116,19 +151,22 @@ class KeyGestureControllerTests { Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) } - private fun notifyHomeGestureCompleted() { - keyGestureController.notifyKeyGestureCompleted(DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H), + private fun notifyHomeGestureCompleted(keyGestureController: KeyGestureController) { + keyGestureController.notifyKeyGestureCompleted( + DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H), KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON, - KeyGestureEvent.KEY_GESTURE_TYPE_HOME) + KeyGestureEvent.KEY_GESTURE_TYPE_HOME + ) } @Test fun testKeyGestureEvent_registerUnregisterListener() { + val keyGestureController = KeyGestureController(context, testLooper.looper) val listener = KeyGestureEventListener() // Register key gesture event listener keyGestureController.registerKeyGestureEventListener(listener, 0) - notifyHomeGestureCompleted() + notifyHomeGestureCompleted(keyGestureController) testLooper.dispatchAll() assertEquals( "Listener should get callbacks on key gesture event completed", @@ -144,7 +182,7 @@ class KeyGestureControllerTests { // Unregister listener events.clear() keyGestureController.unregisterKeyGestureEventListener(listener, 0) - notifyHomeGestureCompleted() + notifyHomeGestureCompleted(keyGestureController) testLooper.dispatchAll() assertEquals( "Listener should not get callback after being unregistered", @@ -155,20 +193,22 @@ class KeyGestureControllerTests { @Test fun testKeyGestureEvent_multipleGestureHandlers() { + val keyGestureController = KeyGestureController(context, testLooper.looper) + // Set up two callbacks. var callbackCount1 = 0 var callbackCount2 = 0 var selfCallback = 0 val externalHandler1 = KeyGestureHandler { _, _ -> - callbackCount1++; + callbackCount1++ true } val externalHandler2 = KeyGestureHandler { _, _ -> - callbackCount2++; + callbackCount2++ true } val selfHandler = KeyGestureHandler { _, _ -> - selfCallback++; + selfCallback++ false } @@ -405,6 +445,14 @@ class KeyGestureControllerTests { KeyEvent.META_META_ON or KeyEvent.META_ALT_ON, intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) ), + TestData( + "META + / -> Open Shortcut Helper", + intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_SLASH), + KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER, + intArrayOf(KeyEvent.KEYCODE_SLASH), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), TestData( "BRIGHTNESS_UP -> Brightness Up", intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP), @@ -528,12 +576,314 @@ class KeyGestureControllerTests { KeyGestureEvent.ACTION_GESTURE_COMPLETE ) ), + TestData( + "CTRL + SPACE -> Switch Language Forward", + intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE), + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + intArrayOf(KeyEvent.KEYCODE_SPACE), + KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "CTRL + SHIFT + SPACE -> Switch Language Backward", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_SHIFT_LEFT, + KeyEvent.KEYCODE_SPACE + ), + KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH, + intArrayOf(KeyEvent.KEYCODE_SPACE), + KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "CTRL + ALT + Z -> Accessibility Shortcut", + intArrayOf( + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_ALT_LEFT, + KeyEvent.KEYCODE_Z + ), + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT, + intArrayOf(KeyEvent.KEYCODE_Z), + KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "SYSRQ -> Take screenshot", + intArrayOf(KeyEvent.KEYCODE_SYSRQ), + KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, + intArrayOf(KeyEvent.KEYCODE_SYSRQ), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), + TestData( + "ESC -> Close All Dialogs", + intArrayOf(KeyEvent.KEYCODE_ESCAPE), + KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS, + intArrayOf(KeyEvent.KEYCODE_ESCAPE), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ), ) } @Test @Parameters(method = "keyGestureEventHandlerTestArguments") fun testKeyGestures(test: TestData) { + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal(keyGestureController, test) + } + + @Test + fun testKeycodesFullyConsumed_irrespectiveOfHandlers() { + val keyGestureController = KeyGestureController(context, testLooper.looper) + val testKeys = intArrayOf( + KeyEvent.KEYCODE_RECENT_APPS, + KeyEvent.KEYCODE_APP_SWITCH, + KeyEvent.KEYCODE_BRIGHTNESS_UP, + KeyEvent.KEYCODE_BRIGHTNESS_DOWN, + KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, + KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, + KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, + KeyEvent.KEYCODE_ALL_APPS, + KeyEvent.KEYCODE_NOTIFICATION, + KeyEvent.KEYCODE_SETTINGS, + KeyEvent.KEYCODE_LANGUAGE_SWITCH, + KeyEvent.KEYCODE_SCREENSHOT, + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_META_RIGHT, + KeyEvent.KEYCODE_ASSIST, + KeyEvent.KEYCODE_VOICE_ASSIST, + KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY, + KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY, + KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY, + KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL, + ) + + val handler = KeyGestureHandler { _, _ -> false } + keyGestureController.registerKeyGestureHandler(handler, 0) + + for (key in testKeys) { + sendKeys(keyGestureController, intArrayOf(key), assertNotSentToApps = true) + } + } + + @Test + fun testSearchKeyGestures_defaultSearch() { + Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior)) + .thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH) + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureNotProduced( + keyGestureController, + "SEARCH -> Default Search", + intArrayOf(KeyEvent.KEYCODE_SEARCH), + ) + } + + @Test + fun testSearchKeyGestures_searchActivity() { + Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior)) + .thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY) + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal( + keyGestureController, + TestData( + "SEARCH -> Launch Search Activity", + intArrayOf(KeyEvent.KEYCODE_SEARCH), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH, + intArrayOf(KeyEvent.KEYCODE_SEARCH), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + fun testSettingKeyGestures_doNothing() { + Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) + .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING) + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureNotProduced( + keyGestureController, + "SETTINGS -> Do Nothing", + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + ) + } + + @Test + fun testSettingKeyGestures_settingsActivity() { + Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) + .thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal( + keyGestureController, + TestData( + "SETTINGS -> Launch Settings Activity", + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS, + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + fun testSettingKeyGestures_notificationPanel() { + Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior)) + .thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal( + keyGestureController, + TestData( + "SETTINGS -> Toggle Notification Panel", + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL, + intArrayOf(KeyEvent.KEYCODE_SETTINGS), + 0, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) + fun testTriggerBugReport() { + Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1") + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal( + keyGestureController, + TestData( + "META + CTRL + DEL -> Trigger Bug Report", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_DEL + ), + KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, + intArrayOf(KeyEvent.KEYCODE_DEL), + KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT) + fun testTriggerBugReport_flagDisabled() { + Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1") + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal( + keyGestureController, + TestData( + "META + CTRL + DEL -> Not Trigger Bug Report (Fallback to BACK)", + intArrayOf( + KeyEvent.KEYCODE_META_LEFT, + KeyEvent.KEYCODE_CTRL_LEFT, + KeyEvent.KEYCODE_DEL + ), + KeyGestureEvent.KEY_GESTURE_TYPE_BACK, + intArrayOf(KeyEvent.KEYCODE_DEL), + KeyEvent.META_META_ON, + intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE) + ) + ) + } + + @Test + fun testCapsLockPressNotified() { + val keyGestureController = KeyGestureController(context, testLooper.looper) + val listener = KeyGestureEventListener() + + keyGestureController.registerKeyGestureEventListener(listener, 0) + sendKeys(keyGestureController, intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK)) + testLooper.dispatchAll() + assertEquals( + "Listener should get callbacks on key gesture event completed", + 1, + events.size + ) + assertEquals( + "Listener should get callback for Toggle Caps Lock key gesture complete event", + KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, + events[0].keyGestureType + ) + } + + @Keep + private fun keyGestureEventHandlerTestArguments_forKeyCombinations(): Array { + return arrayOf( + TestData( + "VOLUME_DOWN + POWER -> Screenshot Chord", + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER), + KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD, + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "POWER + STEM_PRIMARY -> Screenshot Chord", + intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY), + KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD, + intArrayOf(KeyEvent.KEYCODE_POWER, KeyEvent.KEYCODE_STEM_PRIMARY), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "VOLUME_DOWN + VOLUME_UP -> Accessibility Chord", + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP), + KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD, + intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "BACK + DPAD_DOWN -> TV Accessibility Chord", + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), + KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD, + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + TestData( + "BACK + DPAD_CENTER -> TV Trigger Bug Report", + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER), + KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT, + intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER), + 0, + intArrayOf( + KeyGestureEvent.ACTION_GESTURE_START, + KeyGestureEvent.ACTION_GESTURE_COMPLETE + ) + ), + ) + } + + @Test + @Parameters(method = "keyGestureEventHandlerTestArguments_forKeyCombinations") + @EnableFlags( + com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER, + com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES + ) + fun testKeyCombinationGestures(test: TestData) { + val keyGestureController = KeyGestureController(context, testLooper.looper) + testKeyGestureInternal(keyGestureController, test) + } + + private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) { + var handleEvents = mutableListOf() val handler = KeyGestureHandler { event, _ -> handleEvents.add(KeyGestureEvent(event)) true @@ -541,7 +891,7 @@ class KeyGestureControllerTests { keyGestureController.registerKeyGestureHandler(handler, 0) handleEvents.clear() - sendKeys(test.keys, /* assertAllConsumed = */ false) + sendKeys(keyGestureController, test.keys) assertEquals( "Test: $test doesn't produce correct number of key gesture events", @@ -575,55 +925,37 @@ class KeyGestureControllerTests { keyGestureController.unregisterKeyGestureHandler(handler, 0) } - @Test - fun testKeycodesFullyConsumed_irrespectiveOfHandlers() { - val testKeys = intArrayOf( - KeyEvent.KEYCODE_RECENT_APPS, - KeyEvent.KEYCODE_APP_SWITCH, - KeyEvent.KEYCODE_BRIGHTNESS_UP, - KeyEvent.KEYCODE_BRIGHTNESS_DOWN, - KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, - KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, - KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, - KeyEvent.KEYCODE_ALL_APPS, - KeyEvent.KEYCODE_NOTIFICATION, - KeyEvent.KEYCODE_SETTINGS, - KeyEvent.KEYCODE_LANGUAGE_SWITCH, - KeyEvent.KEYCODE_SCREENSHOT, - KeyEvent.KEYCODE_META_LEFT, - KeyEvent.KEYCODE_META_RIGHT, - KeyEvent.KEYCODE_ASSIST, - KeyEvent.KEYCODE_VOICE_ASSIST, - KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY, - KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY, - KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY, - KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL, - ) - - val handler = KeyGestureHandler { _, _ -> false } + private fun testKeyGestureNotProduced( + keyGestureController: KeyGestureController, + testName: String, + testKeys: IntArray + ) { + var handleEvents = mutableListOf() + val handler = KeyGestureHandler { event, _ -> + handleEvents.add(KeyGestureEvent(event)) + true + } keyGestureController.registerKeyGestureHandler(handler, 0) + handleEvents.clear() - for (key in testKeys) { - sendKeys(intArrayOf(key), /* assertAllConsumed = */ true) - } + sendKeys(keyGestureController, testKeys) + assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size) } - private fun sendKeys(testKeys: IntArray, assertAllConsumed: Boolean) { + private fun sendKeys( + keyGestureController: KeyGestureController, + testKeys: IntArray, + assertNotSentToApps: Boolean = false + ) { var metaState = 0 + val now = SystemClock.uptimeMillis() for (key in testKeys) { val downEvent = KeyEvent( - /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_DOWN, key, - 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, - 0 /*flags*/, InputDevice.SOURCE_KEYBOARD + now, now, KeyEvent.ACTION_DOWN, key, 0 /*repeat*/, metaState, + DEVICE_ID, 0 /*scancode*/, 0 /*flags*/, + InputDevice.SOURCE_KEYBOARD ) - val consumed = - keyGestureController.interceptKeyBeforeDispatching(null, downEvent, 0) == -1L - if (assertAllConsumed) { - assertTrue( - "interceptKeyBeforeDispatching should consume all events $downEvent", - consumed - ) - } + interceptKey(keyGestureController, downEvent, assertNotSentToApps) metaState = metaState or MODIFIER.getOrDefault(key, 0) downEvent.recycle() @@ -632,24 +964,39 @@ class KeyGestureControllerTests { for (key in testKeys.reversed()) { val upEvent = KeyEvent( - /* downTime = */0, /* eventTime = */ 0, KeyEvent.ACTION_UP, key, - 0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, - 0 /*flags*/, InputDevice.SOURCE_KEYBOARD + now, now, KeyEvent.ACTION_UP, key, 0 /*repeat*/, metaState, + DEVICE_ID, 0 /*scancode*/, 0 /*flags*/, + InputDevice.SOURCE_KEYBOARD ) - val consumed = - keyGestureController.interceptKeyBeforeDispatching(null, upEvent, 0) == -1L - if (assertAllConsumed) { - assertTrue( - "interceptKeyBeforeDispatching should consume all events $upEvent", - consumed - ) - } + interceptKey(keyGestureController, upEvent, assertNotSentToApps) + metaState = metaState and MODIFIER.getOrDefault(key, 0).inv() upEvent.recycle() testLooper.dispatchAll() } } + private fun interceptKey( + keyGestureController: KeyGestureController, + event: KeyEvent, + assertNotSentToApps: Boolean + ) { + keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE) + testLooper.dispatchAll() + + val consumed = + keyGestureController.interceptKeyBeforeDispatching(null, event, 0) == -1L + if (assertNotSentToApps) { + assertTrue( + "interceptKeyBeforeDispatching should consume all events $event", + consumed + ) + } + if (!consumed) { + keyGestureController.interceptUnhandledKey(event, null) + } + } + inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() { override fun onKeyGestureEvent(event: AidlKeyGestureEvent) { events.add(KeyGestureEvent(event)) diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java index 945907009a9c367f234656be6bfc0209473e1ee1..60fa52f85e348c74b077645bdf597dae25d185b7 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java @@ -57,6 +57,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.Consumer; + /** * Build/Install/Run: * atest TouchpadDebugViewTest @@ -99,10 +101,12 @@ public class TouchpadDebugViewTest { when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice); + Consumer touchpadSwitchHandler = id -> {}; + mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID, new TouchpadHardwareProperties.Builder(0f, 0f, 500f, 500f, 45f, 47f, -4f, 5f, (short) 10, true, - true).build()); + true).build(), touchpadSwitchHandler); mTouchpadDebugView.measure( View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), @@ -321,26 +325,30 @@ public class TouchpadDebugViewTest { new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99)); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), + Color.parseColor("#769763")); mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(84, 85, 169)); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), + Color.parseColor("#5455A9")); mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99)); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), + Color.parseColor("#769763")); // Color should not change because hardware state of a different touchpad mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99)); + assertEquals(((ColorDrawable) child.getBackground()).getColor(), + Color.parseColor("#769763")); } @Test @@ -394,4 +402,73 @@ public class TouchpadDebugViewTest { // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture). verify(mWindowManager, times(0)).updateViewLayout(any(), any()); } -} \ No newline at end of file + + @Test + public void testPinchDrag() { + float offsetY = ViewConfiguration.get(mTestableContext).getScaledTouchSlop() + 10; + + MotionEvent actionDown = new MotionEventBuilder(MotionEvent.ACTION_DOWN, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionDown); + + MotionEvent pointerDown = new MotionEventBuilder(MotionEvent.ACTION_POINTER_DOWN, + SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f) + ) + .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(45f) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(pointerDown); + + // Simulate ACTION_MOVE event (both fingers moving apart). + MotionEvent actionMove = new MotionEventBuilder(MotionEvent.ACTION_MOVE, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f - offsetY) + ) + .rawXCursorPosition(mWindowLayoutParams.x + 10f) + .rawYCursorPosition(mWindowLayoutParams.y + 10f) + .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(45f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionMove); + + MotionEvent pointerUp = new MotionEventBuilder(MotionEvent.ACTION_POINTER_UP, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f - offsetY) + ) + .pointer(new PointerBuilder(1, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(45f + offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(pointerUp); + + MotionEvent actionUp = new MotionEventBuilder(MotionEvent.ACTION_UP, SOURCE_MOUSE) + .pointer(new PointerBuilder(0, MotionEvent.TOOL_TYPE_FINGER) + .x(40f) + .y(40f - offsetY) + ) + .classification(MotionEvent.CLASSIFICATION_PINCH) + .build(); + mTouchpadDebugView.dispatchTouchEvent(actionUp); + + // Verify that no updateViewLayout is called (as expected for a two-finger drag gesture). + verify(mWindowManager, times(0)).updateViewLayout(any(), any()); + } +} diff --git a/tests/Internal/Android.bp b/tests/Internal/Android.bp index 3e58517579b8eeaf1d095ea8cb4748f9ba962993..9f35c7b7fa339643b313826b2d90f177361331da 100644 --- a/tests/Internal/Android.bp +++ b/tests/Internal/Android.bp @@ -32,6 +32,27 @@ android_test { test_suites: ["device-tests"], } +// Run just ApplicationSharedMemoryTest with ABI override for 32 bits. +// This is to test that on systems that support multi-ABI, +// ApplicationSharedMemory works in app processes launched with a different ABI +// than that of the system processes. +android_test { + name: "ApplicationSharedMemoryTest32", + team: "trendy_team_system_performance", + srcs: ["src/com/android/internal/os/ApplicationSharedMemoryTest.java"], + libs: ["android.test.runner.stubs.system"], + static_libs: [ + "junit", + "androidx.test.rules", + "platform-test-annotations", + ], + manifest: "ApplicationSharedMemoryTest32/AndroidManifest.xml", + test_config: "ApplicationSharedMemoryTest32/AndroidTest.xml", + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], +} + android_ravenwood_test { name: "InternalTestsRavenwood", static_libs: [ @@ -45,3 +66,9 @@ android_ravenwood_test { ], auto_gen_config: true, } + +java_test_helper_library { + name: "ApplicationSharedMemoryTestRule", + srcs: ["src/com/android/internal/os/ApplicationSharedMemoryTestRule.java"], + static_libs: ["junit"], +} diff --git a/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml b/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..4e1058ead7349f6000c4952142bd7c8cf3bfa795 --- /dev/null +++ b/tests/Internal/ApplicationSharedMemoryTest32/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml b/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..9bde8b7522e34d9d4e2e184aa611c54b522e5642 --- /dev/null +++ b/tests/Internal/ApplicationSharedMemoryTest32/AndroidTest.xml @@ -0,0 +1,37 @@ + + + + + + + \ No newline at end of file diff --git a/tests/Internal/ApplicationSharedMemoryTest32/OWNERS b/tests/Internal/ApplicationSharedMemoryTest32/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..1ff3fac8ae6f063bb58b25f1d2c6ee7335f57006 --- /dev/null +++ b/tests/Internal/ApplicationSharedMemoryTest32/OWNERS @@ -0,0 +1 @@ +include platform/frameworks/base:/PERFORMANCE_OWNERS \ No newline at end of file diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e3a129fb10593c8afe1d7a42b859923ccabf675b --- /dev/null +++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTest.java @@ -0,0 +1,119 @@ +/* + * 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.os; + +import java.io.IOException; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeTrue; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.junit.Before; + +import java.io.FileDescriptor; + +/** Tests for {@link TimeoutRecord}. */ +@SmallTest +@Presubmit +@RunWith(JUnit4.class) +public class ApplicationSharedMemoryTest { + + @Before + public void setUp() { + // Skip tests if the feature under test is disabled. + assumeTrue(Flags.applicationSharedMemoryEnabled()); + } + + /** + * Every application process, including ours, should have had an instance installed at this + * point. + */ + @Test + public void hasInstance() { + // This shouldn't throw and shouldn't return null. + assertNotNull(ApplicationSharedMemory.getInstance()); + } + + /** Any app process should be able to read shared memory values. */ + @Test + public void canRead() { + ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance(); + instance.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(); + // Don't actually care about the value of the above. + } + + /** Application processes should not have mutable access. */ + @Test + public void appInstanceNotMutable() { + ApplicationSharedMemory instance = ApplicationSharedMemory.getInstance(); + try { + instance.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(17); + fail("Attempted mutation in an app process should throw"); + } catch (Exception expected) { + } + } + + /** Instances share memory if they share the underlying memory region. */ + @Test + public void instancesShareMemory() throws IOException { + ApplicationSharedMemory instance1 = ApplicationSharedMemory.create(); + ApplicationSharedMemory instance2 = + ApplicationSharedMemory.fromFileDescriptor( + instance1.getFileDescriptor(), /* mutable= */ true); + ApplicationSharedMemory instance3 = + ApplicationSharedMemory.fromFileDescriptor( + instance2.getReadOnlyFileDescriptor(), /* mutable= */ false); + + instance1.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(17); + assertEquals( + 17, instance1.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 17, instance2.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 17, instance3.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + + instance2.setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(24); + assertEquals( + 24, instance1.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 24, instance2.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + assertEquals( + 24, instance3.getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()); + } + + /** Can't map read-only memory as mutable. */ + @Test + public void readOnlyCantBeMutable() throws IOException { + ApplicationSharedMemory readWriteInstance = ApplicationSharedMemory.create(); + FileDescriptor readOnlyFileDescriptor = readWriteInstance.getReadOnlyFileDescriptor(); + + try { + ApplicationSharedMemory.fromFileDescriptor(readOnlyFileDescriptor, /* mutable= */ true); + fail("Shouldn't be able to map read-only memory as mutable"); + } catch (Exception expected) { + } + } +} diff --git a/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java new file mode 100644 index 0000000000000000000000000000000000000000..ff2a4611cdf09b44557e19b22db9b59793b7a6fd --- /dev/null +++ b/tests/Internal/src/com/android/internal/os/ApplicationSharedMemoryTestRule.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.os; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; +import com.android.internal.os.ApplicationSharedMemory; + +/** Test rule that sets up and tears down ApplicationSharedMemory for test. */ +public class ApplicationSharedMemoryTestRule implements TestRule { + + private ApplicationSharedMemory mSavedInstance; + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + setup(); + try { + base.evaluate(); // Run the test + } finally { + teardown(); + } + } + }; + } + + private void setup() { + mSavedInstance = ApplicationSharedMemory.sInstance; + ApplicationSharedMemory.sInstance = ApplicationSharedMemory.create(); + } + + private void teardown() { + ApplicationSharedMemory.sInstance.close(); + ApplicationSharedMemory.sInstance = mSavedInstance; + mSavedInstance = null; + } +} diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java index c0e90f9232d6af37edaca3bf4a5e3f589028dd18..8d143b69d124e19b6c81d095cce89b940044eb6c 100644 --- a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java +++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java @@ -727,7 +727,17 @@ public class CrashRecoveryTest { when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW, ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); - + try { + when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { + final PackageInfo res = new PackageInfo(); + res.packageName = inv.getArgument(0); + res.setApexPackageName(res.packageName); + res.setLongVersionCode(VERSION_CODE); + return res; + }); + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } watchdog.registerHealthObserver(rollbackObserver); return rollbackObserver; } @@ -787,8 +797,10 @@ public class CrashRecoveryTest { // Verify controller by default is started when packages are ready assertThat(controller.mIsEnabled).isTrue(); - verify(mConnectivityModuleConnector).registerHealthListener( - mConnectivityModuleCallbackCaptor.capture()); + if (!Flags.refactorCrashrecovery()) { + verify(mConnectivityModuleConnector).registerHealthListener( + mConnectivityModuleCallbackCaptor.capture()); + } } mAllocatedWatchdogs.add(watchdog); return watchdog; diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 5b178250a4c9364a6eeaaa1be7ce469288e15d15..0364781ab064ecf57176c9c3a16d4dec35b3f617 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -46,6 +46,9 @@ import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener; import android.os.Handler; import android.os.SystemProperties; import android.os.test.TestLooper; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.util.AtomicFile; @@ -111,6 +114,9 @@ public class PackageWatchdogTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private final TestClock mTestClock = new TestClock(); private TestLooper mTestLooper; private Context mSpyContext; @@ -966,6 +972,7 @@ public class PackageWatchdogTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY) public void testNetworkStackFailure() { mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); final PackageWatchdog wd = createWatchdog(); @@ -986,6 +993,7 @@ public class PackageWatchdogTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_REFACTOR_CRASHRECOVERY) public void testNetworkStackFailureRecoverabilityDetection() { final PackageWatchdog wd = createWatchdog(); @@ -1758,8 +1766,10 @@ public class PackageWatchdogTest { // Verify controller by default is started when packages are ready assertThat(controller.mIsEnabled).isTrue(); - verify(mConnectivityModuleConnector).registerHealthListener( - mConnectivityModuleCallbackCaptor.capture()); + if (!Flags.refactorCrashrecovery()) { + verify(mConnectivityModuleConnector).registerHealthListener( + mConnectivityModuleCallbackCaptor.capture()); + } } mAllocatedWatchdogs.add(watchdog); return watchdog; diff --git a/tests/Tracing/TEST_MAPPING b/tests/Tracing/TEST_MAPPING index 7f58fceee24dc82fdfa9e248c2f4c65036730b05..f6e5221b721b1d0e88c4e327a10178fbeb0ff6d9 100644 --- a/tests/Tracing/TEST_MAPPING +++ b/tests/Tracing/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "TracingTests" } diff --git a/tests/broadcasts/OWNERS b/tests/broadcasts/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..d2e1f815e8dcdfc1d7015f9f5d8cee8df6d23c54 --- /dev/null +++ b/tests/broadcasts/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 316181 +include platform/frameworks/base:/BROADCASTS_OWNERS diff --git a/tests/broadcasts/unit/Android.bp b/tests/broadcasts/unit/Android.bp new file mode 100644 index 0000000000000000000000000000000000000000..47166a7135807523f4b53567906235a31d767b22 --- /dev/null +++ b/tests/broadcasts/unit/Android.bp @@ -0,0 +1,45 @@ +// +// 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"], + default_team: "trendy_team_framework_backstage_power", +} + +android_test { + name: "BroadcastUnitTests", + srcs: ["src/**/*.java"], + defaults: [ + "modules-utils-extended-mockito-rule-defaults", + ], + static_libs: [ + "androidx.test.runner", + "androidx.test.rules", + "androidx.test.ext.junit", + "mockito-target-extended-minus-junit4", + "truth", + "flag-junit", + "android.app.flags-aconfig-java", + ], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], +} diff --git a/tests/broadcasts/unit/AndroidManifest.xml b/tests/broadcasts/unit/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..e9c5248e4d9856822c74755599f16c7b0c12ed75 --- /dev/null +++ b/tests/broadcasts/unit/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/broadcasts/unit/AndroidTest.xml b/tests/broadcasts/unit/AndroidTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..b91e4783b69ef41015a8b7a3fa5cbaebccc36a78 --- /dev/null +++ b/tests/broadcasts/unit/AndroidTest.xml @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING new file mode 100644 index 0000000000000000000000000000000000000000..0e824c54e92e5435085515fd93aac0cfa5ed9ab8 --- /dev/null +++ b/tests/broadcasts/unit/TEST_MAPPING @@ -0,0 +1,15 @@ +{ + "postsubmit": [ + { + "name": "BroadcastUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } + ] +} diff --git a/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b7c412dea999a2de069aee891a111f7b78b48566 --- /dev/null +++ b/tests/broadcasts/unit/src/android/app/BroadcastStickyCacheTest.java @@ -0,0 +1,258 @@ +/* + * 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; + +import static android.content.Intent.ACTION_BATTERY_CHANGED; +import static android.content.Intent.ACTION_DEVICE_STORAGE_LOW; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; + +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.Bundle; +import android.os.SystemProperties; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.util.ArrayMap; + +import androidx.annotation.GuardedBy; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.modules.utils.testing.ExtendedMockitoRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +@EnableFlags(Flags.FLAG_USE_STICKY_BCAST_CACHE) +@RunWith(AndroidJUnit4.class) +@SmallTest +public class BroadcastStickyCacheTest { + @ClassRule + public static final SetFlagsRule.ClassRule mClassRule = new SetFlagsRule.ClassRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = mClassRule.createSetFlagsRule(); + + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) + .mockStatic(SystemProperties.class) + .build(); + + private static final String PROP_KEY_BATTERY_CHANGED = BroadcastStickyCache.getKey( + ACTION_BATTERY_CHANGED); + + private final TestSystemProps mTestSystemProps = new TestSystemProps(); + + @Before + public void setUp() { + doAnswer(invocation -> { + final String name = invocation.getArgument(0); + final long value = Long.parseLong(invocation.getArgument(1)); + mTestSystemProps.add(name, value); + return null; + }).when(() -> SystemProperties.set(anyString(), anyString())); + doAnswer(invocation -> { + final String name = invocation.getArgument(0); + final TestSystemProps.Handle testHandle = mTestSystemProps.query(name); + if (testHandle == null) { + return null; + } + final SystemProperties.Handle handle = Mockito.mock(SystemProperties.Handle.class); + doAnswer(handleInvocation -> testHandle.getLong(-1)).when(handle).getLong(anyLong()); + return handle; + }).when(() -> SystemProperties.find(anyString())); + } + + @After + public void tearDown() { + mTestSystemProps.clear(); + BroadcastStickyCache.clearForTest(); + } + + @Test + public void testUseCache_nullFilter() { + assertThat(BroadcastStickyCache.useCache(null)).isEqualTo(false); + } + + @Test + public void testUseCache_noActions() { + final IntentFilter filter = new IntentFilter(); + assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); + } + + @Test + public void testUseCache_multipleActions() { + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_DEVICE_STORAGE_LOW); + filter.addAction(ACTION_BATTERY_CHANGED); + assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); + } + + @Test + public void testUseCache_valueNotSet() { + final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); + assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); + } + + @Test + public void testUseCache() { + final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); + final Intent intent = new Intent(ACTION_BATTERY_CHANGED) + .putExtra(BatteryManager.EXTRA_LEVEL, 90); + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + BroadcastStickyCache.add(filter, intent); + assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true); + } + + @Test + public void testUseCache_versionMismatch() { + final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); + final Intent intent = new Intent(ACTION_BATTERY_CHANGED) + .putExtra(BatteryManager.EXTRA_LEVEL, 90); + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + BroadcastStickyCache.add(filter, intent); + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + + assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(false); + } + + @Test + public void testAdd() { + final IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); + Intent intent = new Intent(ACTION_BATTERY_CHANGED) + .putExtra(BatteryManager.EXTRA_LEVEL, 90); + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + BroadcastStickyCache.add(filter, intent); + assertThat(BroadcastStickyCache.useCache(filter)).isEqualTo(true); + Intent actualIntent = BroadcastStickyCache.getIntentUnchecked(filter); + assertThat(actualIntent).isNotNull(); + assertEquals(actualIntent, intent); + + intent = new Intent(ACTION_BATTERY_CHANGED) + .putExtra(BatteryManager.EXTRA_LEVEL, 99); + BroadcastStickyCache.add(filter, intent); + actualIntent = BroadcastStickyCache.getIntentUnchecked(filter); + assertThat(actualIntent).isNotNull(); + assertEquals(actualIntent, intent); + } + + @Test + public void testIncrementVersion_propExists() { + SystemProperties.set(PROP_KEY_BATTERY_CHANGED, String.valueOf(100)); + + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(101); + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(102); + } + + @Test + public void testIncrementVersion_propNotExists() { + // Verify that the property doesn't exist + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); + + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(1); + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2); + } + + @Test + public void testIncrementVersionIfExists_propExists() { + BroadcastStickyCache.incrementVersion(ACTION_BATTERY_CHANGED); + + BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(2); + BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(3); + } + + @Test + public void testIncrementVersionIfExists_propNotExists() { + // Verify that the property doesn't exist + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); + + BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); + // Verify that property is not added as part of the querying. + BroadcastStickyCache.incrementVersionIfExists(ACTION_BATTERY_CHANGED); + assertThat(mTestSystemProps.get(PROP_KEY_BATTERY_CHANGED, -1 /* def */)).isEqualTo(-1); + } + + private void assertEquals(Intent actualIntent, Intent expectedIntent) { + assertThat(actualIntent.getAction()).isEqualTo(expectedIntent.getAction()); + assertEquals(actualIntent.getExtras(), expectedIntent.getExtras()); + } + + private void assertEquals(Bundle actualExtras, Bundle expectedExtras) { + assertWithMessage("Extras expected=%s, actual=%s", expectedExtras, actualExtras) + .that(actualExtras.kindofEquals(expectedExtras)).isTrue(); + } + + private static final class TestSystemProps { + @GuardedBy("mSysProps") + private final ArrayMap mSysProps = new ArrayMap<>(); + + public void add(String name, long value) { + synchronized (mSysProps) { + mSysProps.put(name, value); + } + } + + public long get(String name, long defaultValue) { + synchronized (mSysProps) { + final int idx = mSysProps.indexOfKey(name); + return idx >= 0 ? mSysProps.valueAt(idx) : defaultValue; + } + } + + public Handle query(String name) { + synchronized (mSysProps) { + return mSysProps.containsKey(name) ? new Handle(name) : null; + } + } + + public void clear() { + synchronized (mSysProps) { + mSysProps.clear(); + } + } + + public class Handle { + private final String mName; + + Handle(String name) { + mName = name; + } + + public long getLong(long defaultValue) { + return get(mName, defaultValue); + } + } + } +} diff --git a/tests/testables/Android.bp b/tests/testables/Android.bp index 7596ee722d010a7c7715426a462a866e211730c6..f2111856c66666ed9a256faa871a143e4a3a36a5 100644 --- a/tests/testables/Android.bp +++ b/tests/testables/Android.bp @@ -25,7 +25,10 @@ package { java_library { name: "testables", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], libs: [ "android.test.runner.stubs.system", "android.test.mock.stubs.system", diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java index 37b39c314e532bb6700baa6d5beed2261d47c27d..10df17f991d363b0e0c6d1ffa85d19599010ebd6 100644 --- a/tests/testables/src/android/testing/TestWithLooperRule.java +++ b/tests/testables/src/android/testing/TestWithLooperRule.java @@ -34,13 +34,13 @@ import java.util.List; * Looper for the Statement. */ public class TestWithLooperRule implements MethodRule { - /* * This rule requires to be the inner most Rule, so the next statement is RunAfters * instead of another rule. You can set it by '@Rule(order = Integer.MAX_VALUE)' */ @Override public Statement apply(Statement base, FrameworkMethod method, Object target) { + // getting testRunner check, if AndroidTestingRunning then we skip this rule RunWith runWithAnnotation = target.getClass().getAnnotation(RunWith.class); if (runWithAnnotation != null) { @@ -97,6 +97,9 @@ public class TestWithLooperRule implements MethodRule { case "InvokeParameterizedMethod": this.wrapFieldMethodFor(next, "frameworkMethod", method, target); return; + case "ExpectException": + next = this.getNextStatement(next, "next"); + break; default: throw new Exception( String.format("Unexpected Statement received: [%s]", diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp index 1eb36fa5f9087ca4cf634e83b0e45291fe73c8b5..c23f41a6c3d4012d5f939a9eb627ae712bb72c62 100644 --- a/tests/testables/tests/Android.bp +++ b/tests/testables/tests/Android.bp @@ -34,6 +34,7 @@ android_test { "androidx.core_core-animation", "androidx.core_core-ktx", "androidx.test.rules", + "androidx.test.ext.junit", "hamcrest-library", "mockito-target-inline-minus-junit4", "testables", diff --git a/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java new file mode 100644 index 0000000000000000000000000000000000000000..b7d5e0e129422d5ae0bc6776353230511e0d8f16 --- /dev/null +++ b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java @@ -0,0 +1,42 @@ +/* + * 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.testing; + +import android.testing.TestableLooper.RunWithLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test that TestableLooper now handles expected exceptions in tests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +@RunWithLooper +public class TestableLooperJUnit4Test { + @Rule + public final TestWithLooperRule mTestWithLooperRule = new TestWithLooperRule(); + + @Test(expected = Exception.class) + public void testException() throws Exception { + throw new Exception("this exception is expected"); + } +} + diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index df1d51e3766028db8e032a6d7ab9493921f6b77a..064b4617b0a22a5de425d3aa3daacac591369d56 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -346,6 +346,21 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& value->value->Accept(&body_printer); printer->Undent(); } + printer->Println("Flag disabled values:"); + for (const auto& value : entry.flag_disabled_values) { + printer->Print("("); + printer->Print(value->config.to_string()); + printer->Print(") "); + value->value->Accept(&headline_printer); + if (options.show_sources && !value->value->GetSource().path.empty()) { + printer->Print(" src="); + printer->Print(value->value->GetSource().to_string()); + } + printer->Println(); + printer->Indent(); + value->value->Accept(&body_printer); + printer->Undent(); + } printer->Undent(); } } diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index a274f047586cfb35fc0184779875ca859862d6fb..0d261abd728d46fe9faeb40459bbabc980af5248 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -71,6 +71,17 @@ enum class ResourceType { enum class FlagStatus { NoFlag = 0, Disabled = 1, Enabled = 2 }; +struct FeatureFlagAttribute { + std::string name; + bool negated = false; + + std::string ToString() { + return (negated ? "!" : "") + name; + } + + bool operator==(const FeatureFlagAttribute& o) const = default; +}; + android::StringPiece to_string(ResourceType type); /** @@ -232,6 +243,12 @@ struct ResourceFile { // Exported symbols std::vector exported_symbols; + + // Flag status + FlagStatus flag_status = FlagStatus::NoFlag; + + // Flag + std::optional flag; }; /** diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a5aecc8557070cb1e90fc962617415f6ae7b73c0..fce6aa7c80d9c868036e90e810a4178202f3c329 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -107,9 +107,10 @@ struct ParsedResource { Visibility::Level visibility_level = Visibility::Level::kUndefined; bool staged_api = false; bool allow_new = false; - FlagStatus flag_status = FlagStatus::NoFlag; std::optional overlayable_item; std::optional staged_alias; + std::optional flag; + FlagStatus flag_status; std::string comment; std::unique_ptr value; @@ -151,6 +152,7 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia } if (res->value != nullptr) { + res->value->SetFlag(res->flag); res->value->SetFlagStatus(res->flag_status); // Attach the comment, source and config to the value. res->value->SetComment(std::move(res->comment)); @@ -162,8 +164,6 @@ static bool AddResourcesToTable(ResourceTable* table, android::IDiagnostics* dia res_builder.SetStagedId(res->staged_alias.value()); } - res_builder.SetFlagStatus(res->flag_status); - bool error = false; if (!res->name.entry.empty()) { if (!table->AddResource(res_builder.Build(), diag)) { @@ -546,12 +546,26 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, {"symbol", std::mem_fn(&ResourceParser::ParseSymbol)}, }); - std::string resource_type = parser->element_name(); - auto flag_status = GetFlagStatus(parser); - if (!flag_status) { - return false; + std::string_view resource_type = parser->element_name(); + if (auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"))) { + if (options_.flag) { + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) + << "Resource flag are not allowed both in the path and in the file"); + return false; + } + out_resource->flag = std::move(flag); + std::string error; + auto flag_status = GetFlagStatus(out_resource->flag, options_.feature_flag_values, &error); + if (flag_status) { + out_resource->flag_status = flag_status.value(); + } else { + diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << error); + return false; + } + } else if (options_.flag) { + out_resource->flag = options_.flag; + out_resource->flag_status = options_.flag_status; } - out_resource->flag_status = flag_status.value(); // The value format accepted for this resource. uint32_t resource_format = 0u; @@ -567,7 +581,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // Items have their type encoded in the type attribute. if (std::optional maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { - resource_type = std::string(maybe_type.value()); + resource_type = maybe_type.value(); } else { diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << " must have a 'type' attribute"); @@ -590,7 +604,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, // Bags have their type encoded in the type attribute. if (std::optional maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { - resource_type = std::string(maybe_type.value()); + resource_type = maybe_type.value(); } else { diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << " must have a 'type' attribute"); @@ -733,33 +747,6 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, return false; } -std::optional ResourceParser::GetFlagStatus(xml::XmlPullParser* parser) { - auto flag_status = FlagStatus::NoFlag; - - std::optional flag = xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"); - if (flag) { - auto flag_it = options_.feature_flag_values.find(flag.value()); - if (flag_it == options_.feature_flag_values.end()) { - diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) - << "Resource flag value undefined"); - return {}; - } - const auto& flag_properties = flag_it->second; - if (!flag_properties.read_only) { - diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) - << "Only read only flags may be used with resources"); - return {}; - } - if (!flag_properties.enabled.has_value()) { - diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) - << "Only flags with a value may be used with resources"); - return {}; - } - flag_status = flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled; - } - return flag_status; -} - bool ResourceParser::ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, const uint32_t format) { @@ -1666,21 +1653,25 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, const std::string& element_namespace = parser->element_namespace(); const std::string& element_name = parser->element_name(); if (element_namespace.empty() && element_name == "item") { - auto flag_status = GetFlagStatus(parser); - if (!flag_status) { - error = true; - continue; - } + auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag")); std::unique_ptr item = ParseXml(parser, typeMask, kNoRawString); if (!item) { diag_->Error(android::DiagMessage(item_source) << "could not parse array item"); error = true; continue; } - item->SetFlagStatus(flag_status.value()); + item->SetFlag(flag); + std::string err; + auto status = GetFlagStatus(flag, options_.feature_flag_values, &err); + if (status) { + item->SetFlagStatus(status.value()); + } else { + diag_->Error(android::DiagMessage(item_source) << err); + error = true; + continue; + } item->SetSource(item_source); array->elements.emplace_back(std::move(item)); - } else if (!ShouldIgnoreElement(element_namespace, element_name)) { diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << "unknown tag <" << element_namespace << ":" << element_name << ">"); diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 442dea89ef40b24aac2116d6a772df53244f5bc0..90690d522ef2b4c3f9bc4d414007e7e87ec900ce 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -57,6 +57,11 @@ struct ResourceParserOptions { std::optional visibility; FeatureFlagValues feature_flag_values; + + // The flag that should be applied to all resources parsed + std::optional flag; + + FlagStatus flag_status = FlagStatus::NoFlag; }; struct FlattenedXmlSubTree { @@ -85,8 +90,6 @@ class ResourceParser { private: DISALLOW_COPY_AND_ASSIGN(ResourceParser); - std::optional GetFlagStatus(xml::XmlPullParser* parser); - std::optional CreateFlattenSubTree(xml::XmlPullParser* parser); // Parses the XML subtree as a StyleString (flattened XML representation for strings with diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 97514599c0b183bcc277188cbfafc25d88d8c1e6..5435cba290fc67059f8401bed4af80bcb20b0e56 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -101,6 +101,21 @@ struct lt_config_key_ref { } }; +struct ConfigFlagKey { + const ConfigDescription* config; + StringPiece product; + const FeatureFlagAttribute& flag; +}; + +struct lt_config_flag_key_ref { + template + bool operator()(const T& lhs, const ConfigFlagKey& rhs) const noexcept { + return std::tie(lhs->config, lhs->product, lhs->value->GetFlag()->name, + lhs->value->GetFlag()->negated) < + std::tie(*rhs.config, rhs.product, rhs.flag.name, rhs.flag.negated); + } +}; + } // namespace ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) { @@ -213,6 +228,25 @@ std::vector ResourceEntry::FindAllValues(const ConfigDescr return results; } +ResourceConfigValue* ResourceEntry::FindOrCreateFlagDisabledValue( + const FeatureFlagAttribute& flag, const android::ConfigDescription& config, + android::StringPiece product) { + auto iter = std::lower_bound(flag_disabled_values.begin(), flag_disabled_values.end(), + ConfigFlagKey{&config, product, flag}, lt_config_flag_key_ref()); + if (iter != flag_disabled_values.end()) { + ResourceConfigValue* value = iter->get(); + const auto value_flag = value->value->GetFlag().value(); + if (value_flag.name == flag.name && value_flag.negated == flag.negated && + value->config == config && value->product == product) { + return value; + } + } + ResourceConfigValue* newValue = + flag_disabled_values.insert(iter, util::make_unique(config, product)) + ->get(); + return newValue; +} + bool ResourceEntry::HasDefaultValue() const { // The default config should be at the top of the list, since the list is sorted. return !values.empty() && values.front()->config == ConfigDescription::DefaultConfig(); @@ -375,13 +409,14 @@ struct EntryViewComparer { } }; -void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package, - const ResourceTableType* type, const std::string& entry_name, - const std::optional& id, const Visibility& visibility, - const std::optional& allow_new, - const std::optional& overlayable_item, - const std::optional& staged_id, - const std::vector>& values) { +void InsertEntryIntoTableView( + ResourceTableView& table, const ResourceTablePackage* package, const ResourceTableType* type, + const std::string& entry_name, const std::optional& id, + const Visibility& visibility, const std::optional& allow_new, + const std::optional& overlayable_item, + const std::optional& staged_id, + const std::vector>& values, + const std::vector>& flag_disabled_values) { SortedVectorInserter package_inserter; SortedVectorInserter type_inserter; SortedVectorInserter entry_inserter; @@ -408,6 +443,9 @@ void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePacka for (auto& value : values) { new_entry.values.emplace_back(value.get()); } + for (auto& value : flag_disabled_values) { + new_entry.flag_disabled_values.emplace_back(value.get()); + } entry_inserter.Insert(view_type->entries, std::move(new_entry)); } @@ -426,6 +464,21 @@ const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescrip return nullptr; } +const ResourceConfigValue* ResourceTableEntryView::FindFlagDisabledValue( + const FeatureFlagAttribute& flag, const ConfigDescription& config, + android::StringPiece product) const { + auto iter = std::lower_bound(flag_disabled_values.begin(), flag_disabled_values.end(), + ConfigFlagKey{&config, product, flag}, lt_config_flag_key_ref()); + if (iter != values.end()) { + const ResourceConfigValue* value = *iter; + if (value->value->GetFlag() == flag && value->config == config && + StringPiece(value->product) == product) { + return value; + } + } + return nullptr; +} + ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const { ResourceTableView view; for (const auto& package : packages) { @@ -433,13 +486,13 @@ ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptio for (const auto& entry : type->entries) { InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id, entry->visibility, entry->allow_new, entry->overlayable_item, - entry->staged_id, entry->values); + entry->staged_id, entry->values, entry->flag_disabled_values); if (options.create_alias_entries && entry->staged_id) { auto alias_id = entry->staged_id.value().id; InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id, entry->visibility, entry->allow_new, entry->overlayable_item, {}, - entry->values); + entry->values, entry->flag_disabled_values); } } } @@ -587,6 +640,25 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) entry->staged_id = res.staged_id.value(); } + if (res.value != nullptr && res.value->GetFlagStatus() == FlagStatus::Disabled) { + auto disabled_config_value = + entry->FindOrCreateFlagDisabledValue(res.value->GetFlag().value(), res.config, res.product); + if (!disabled_config_value->value) { + // Resource does not exist, add it now. + // Must clone the value since it might be in the values vector as well + CloningValueTransformer cloner(&string_pool); + disabled_config_value->value = res.value->Transform(cloner); + } else { + diag->Error(android::DiagMessage(source) + << "duplicate value for resource '" << res.name << "' " << "with config '" + << res.config << "' and flag '" + << (res.value->GetFlag().value().negated ? "!" : "") + << res.value->GetFlag().value().name << "'"); + diag->Error(android::DiagMessage(source) << "resource previously defined here"); + return false; + } + } + if (res.value != nullptr) { auto config_value = entry->FindOrCreateValue(res.config, res.product); if (!config_value->value) { @@ -595,9 +667,9 @@ bool ResourceTable::AddResource(NewResource&& res, android::IDiagnostics* diag) } else { // When validation is enabled, ensure that a resource cannot have multiple values defined for // the same configuration unless protected by flags. - auto result = - validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(), res.flag_status) - : CollisionResult::kKeepBoth; + auto result = validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(), + res.value->GetFlagStatus()) + : CollisionResult::kKeepBoth; if (result == CollisionResult::kConflict) { result = ResolveValueCollision(config_value->value.get(), res.value.get()); } @@ -771,11 +843,6 @@ NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) { return *this; } -NewResourceBuilder& NewResourceBuilder::SetFlagStatus(FlagStatus flag_status) { - res_.flag_status = flag_status; - return *this; -} - NewResource NewResourceBuilder::Build() { return std::move(res_); } diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index cba6b70cfbd646ac7db123d3a3bbc9b014184bdf..b0e185536d16cbc52a9f9758a92e4ab2343437b2 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -136,6 +136,9 @@ class ResourceEntry { // The resource's values for each configuration. std::vector> values; + // The resource's values that are behind disabled flags. + std::vector> flag_disabled_values; + explicit ResourceEntry(android::StringPiece name) : name(name) { } @@ -148,6 +151,13 @@ class ResourceEntry { android::StringPiece product); std::vector FindAllValues(const android::ConfigDescription& config); + // Either returns the existing ResourceConfigValue in the disabled list with the given flag, + // config, and product or creates a new one and returns that. In either case the returned value + // does not have the flag set on the value so it must be set by the caller. + ResourceConfigValue* FindOrCreateFlagDisabledValue(const FeatureFlagAttribute& flag, + const android::ConfigDescription& config, + android::StringPiece product = {}); + template std::vector FindValuesIf(Func f) { std::vector results; @@ -215,9 +225,14 @@ struct ResourceTableEntryView { std::optional overlayable_item; std::optional staged_id; std::vector values; + std::vector flag_disabled_values; const ResourceConfigValue* FindValue(const android::ConfigDescription& config, android::StringPiece product = {}) const; + + const ResourceConfigValue* FindFlagDisabledValue(const FeatureFlagAttribute& flag, + const android::ConfigDescription& config, + android::StringPiece product = {}) const; }; struct ResourceTableTypeView { @@ -269,7 +284,6 @@ struct NewResource { std::optional allow_new; std::optional staged_id; bool allow_mangled = false; - FlagStatus flag_status = FlagStatus::NoFlag; }; struct NewResourceBuilder { @@ -283,7 +297,6 @@ struct NewResourceBuilder { NewResourceBuilder& SetAllowNew(AllowNew allow_new); NewResourceBuilder& SetStagedId(StagedId id); NewResourceBuilder& SetAllowMangled(bool allow_mangled); - NewResourceBuilder& SetFlagStatus(FlagStatus flag_status); NewResource Build(); private: diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index b75e87c90128bc70596570f18522558cf48ebc50..723cfc0e035bec8cdb7d7faddf51881232a5cfa4 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -1102,6 +1102,7 @@ template std::unique_ptr CopyValueFields(std::unique_ptr new_value, const T* value) { new_value->SetSource(value->GetSource()); new_value->SetComment(value->GetComment()); + new_value->SetFlag(value->GetFlag()); new_value->SetFlagStatus(value->GetFlagStatus()); return new_value; } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index a1b1839b19ef4406edf5e6d845e9b650f5f1b0f3..e000c653b87a2b3c79c96b4f5b050014f01d669e 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -65,10 +65,21 @@ class Value { return translatable_; } + void SetFlag(std::optional val) { + flag_ = val; + } + + std::optional GetFlag() const { + return flag_; + } + void SetFlagStatus(FlagStatus val) { flag_status_ = val; } + // If the value is behind a flag this returns whether that flag was enabled when the value was + // parsed by comparing it to the flags passed on the command line to aapt2 (taking into account + // negation if necessary). If there was no flag, FlagStatus::NoFlag is returned instead. FlagStatus GetFlagStatus() const { return flag_status_; } @@ -128,6 +139,7 @@ class Value { std::string comment_; bool weak_ = false; bool translatable_ = true; + std::optional flag_; FlagStatus flag_status_ = FlagStatus::NoFlag; private: diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 5c6408940b343b36148eb9abf1ccfa3a2f4f8332..a0f60b62db3a90e580d60185591e52681859c20c 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -240,6 +240,9 @@ message Entry { // The staged resource ID of this finalized resource. StagedId staged_id = 7; + + // The set of values defined for this entry which are behind disabled flags + repeated ConfigValue flag_disabled_config_value = 8; } // A Configuration/Value pair. @@ -283,6 +286,8 @@ message Item { // The status of the flag the value is behind if any uint32 flag_status = 8; + bool flag_negated = 9; + string flag_name = 10; } // A CompoundValue is an abstract type. It represents a value that is a made of other values. diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto index b0ed3da333686901f5bda22e46064b4545d54ee5..f4735a2f6ce7492ed46088e7804d539d7e3ab36f 100644 --- a/tools/aapt2/ResourcesInternal.proto +++ b/tools/aapt2/ResourcesInternal.proto @@ -49,4 +49,9 @@ message CompiledFile { // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file). repeated Symbol exported_symbol = 5; + + // The status of the flag the file is behind if any + uint32 flag_status = 6; + bool flag_negated = 7; + string flag_name = 8; } diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 2a978a5153cace857436062f4e3017ae65d19039..52372fa385251d0354e01aef172f35fea3251077 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -67,6 +67,7 @@ struct ResourcePathData { std::string resource_dir; std::string name; std::string extension; + std::string flag_name; // Original config str. We keep this because when we parse the config, we may add on // version qualifiers. We want to preserve the original input so the output is easily @@ -81,6 +82,22 @@ static std::optional ExtractResourcePathData(const std::string std::string* out_error, const CompileOptions& options) { std::vector parts = util::Split(path, dir_sep); + + std::string flag_name; + // Check for a flag + for (auto iter = parts.begin(); iter != parts.end();) { + if (iter->starts_with("flag(") && iter->ends_with(")")) { + if (!flag_name.empty()) { + if (out_error) *out_error = "resource path cannot contain more than one flag directory"; + return {}; + } + flag_name = iter->substr(5, iter->size() - 6); + iter = parts.erase(iter); + } else { + ++iter; + } + } + if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; return {}; @@ -131,6 +148,7 @@ static std::optional ExtractResourcePathData(const std::string std::string(dir_str), std::string(name), std::string(extension), + std::move(flag_name), std::string(config_str), config}; } @@ -142,6 +160,9 @@ static std::string BuildIntermediateContainerFilename(const ResourcePathData& da name << "-" << data.config_str; } name << "_" << data.name; + if (!data.flag_name.empty()) { + name << ".(" << data.flag_name << ")"; + } if (!data.extension.empty()) { name << "." << data.extension; } @@ -163,7 +184,6 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, << "failed to open file: " << fin->GetError()); return false; } - // Parse the values file from XML. xml::XmlPullParser xml_parser(fin.get()); @@ -176,6 +196,18 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // If visibility was forced, we need to use it when creating a new resource and also error if // we try to parse the , , or tags. parser_options.visibility = options.visibility; + parser_options.flag = ParseFlag(path_data.flag_name); + + if (parser_options.flag) { + std::string error; + auto flag_status = GetFlagStatus(parser_options.flag, options.feature_flag_values, &error); + if (flag_status) { + parser_options.flag_status = std::move(flag_status.value()); + } else { + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error); + return false; + } + } ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config, parser_options); @@ -402,6 +434,18 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, xmlres->file.config = path_data.config; xmlres->file.source = path_data.source; xmlres->file.type = ResourceFile::Type::kProtoXml; + xmlres->file.flag = ParseFlag(path_data.flag_name); + + if (xmlres->file.flag) { + std::string error; + auto flag_status = GetFlagStatus(xmlres->file.flag, options.feature_flag_values, &error); + if (flag_status) { + xmlres->file.flag_status = flag_status.value(); + } else { + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error); + return false; + } + } // Collect IDs that are defined here. XmlIdCollector collector; @@ -491,6 +535,27 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, res_file.source = path_data.source; res_file.type = ResourceFile::Type::kPng; + if (!path_data.flag_name.empty()) { + FeatureFlagAttribute flag; + auto name = path_data.flag_name; + if (name.starts_with('!')) { + flag.negated = true; + flag.name = name.substr(1); + } else { + flag.name = name; + } + res_file.flag = flag; + + std::string error; + auto flag_status = GetFlagStatus(flag, options.feature_flag_values, &error); + if (flag_status) { + res_file.flag_status = flag_status.value(); + } else { + context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error); + return false; + } + } + { auto data = file->OpenAsData(); if (!data) { diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index 6da3176b2bee46be2c25a8b1631704892caa8dc2..d3750a6100d3c4878b0e01ceece88634408054fe 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -138,6 +138,22 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a, } } + for (const ResourceConfigValue* config_value_a : entry_a.flag_disabled_values) { + auto config_value_b = entry_b.FindFlagDisabledValue(config_value_a->value->GetFlag().value(), + config_value_a->config); + if (!config_value_b) { + std::stringstream str_stream; + str_stream << "missing disabled value " << pkg_a.name << ":" << type_a.named_type << "/" + << entry_a.name << " config=" << config_value_a->config + << " flag=" << config_value_a->value->GetFlag()->ToString(); + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } else { + diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a, + apk_b, pkg_b, type_b, entry_b, config_value_b); + } + } + // Check for any newly added config values. for (const ResourceConfigValue* config_value_b : entry_b.values) { auto config_value_a = entry_a.FindValue(config_value_b->config); @@ -149,6 +165,18 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a, diff = true; } } + for (const ResourceConfigValue* config_value_b : entry_b.flag_disabled_values) { + auto config_value_a = entry_a.FindFlagDisabledValue(config_value_b->value->GetFlag().value(), + config_value_b->config); + if (!config_value_a) { + std::stringstream str_stream; + str_stream << "new disabled config " << pkg_b.name << ":" << type_b.named_type << "/" + << entry_b.name << " config=" << config_value_b->config + << " flag=" << config_value_b->value->GetFlag()->ToString(); + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; + } + } return diff; } diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 7739171b347f0445657be84d4d4884152b92389f..08f8f0d85807f439b9417c4f5e69c42b0df4a9da 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -34,6 +34,44 @@ using ::android::base::StringPrintf; namespace aapt { +std::optional ParseFlag(std::optional flag_text) { + if (!flag_text || flag_text->empty()) { + return {}; + } + FeatureFlagAttribute flag; + if (flag_text->starts_with('!')) { + flag.negated = true; + flag.name = flag_text->substr(1); + } else { + flag.name = flag_text.value(); + } + return flag; +} + +std::optional GetFlagStatus(const std::optional& flag, + const FeatureFlagValues& feature_flag_values, + std::string* out_err) { + if (!flag) { + return FlagStatus::NoFlag; + } + auto flag_it = feature_flag_values.find(flag->name); + if (flag_it == feature_flag_values.end()) { + *out_err = "Resource flag value undefined: " + flag->name; + return {}; + } + const auto& flag_properties = flag_it->second; + if (!flag_properties.read_only) { + *out_err = "Only read only flags may be used with resources: " + flag->name; + return {}; + } + if (!flag_properties.enabled.has_value()) { + *out_err = "Only flags with a value may be used with resources: " + flag->name; + return {}; + } + return (flag_properties.enabled.value() != flag->negated) ? FlagStatus::Enabled + : FlagStatus::Disabled; +} + std::optional ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) { ConfigDescription preferred_density_config; if (!ConfigDescription::Parse(arg, &preferred_density_config)) { diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h index 6b8813b3408224c7fcfd5b599d1f74fd614190bb..d32e532b86a8d82d82eeadf33939192e8bb73d95 100644 --- a/tools/aapt2/cmd/Util.h +++ b/tools/aapt2/cmd/Util.h @@ -49,6 +49,12 @@ struct FeatureFlagProperties { using FeatureFlagValues = std::map>; +std::optional ParseFlag(std::optional flag_text); + +std::optional GetFlagStatus(const std::optional& flag, + const FeatureFlagValues& feature_flag_values, + std::string* out_err); + // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc). // Returns Nothing and logs a human friendly error message if the string was not legal. std::optional ParseTargetDensityParameter(android::StringPiece arg, diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index 55f5e5668a16563be0afbae7728056cb87700377..8583cadff6d2d1985977ba0865081424208a0f7d 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -536,6 +536,34 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config, &out_table->string_pool, files, out_error); + + if (config_value->value == nullptr) { + return false; + } + } + + // flag disabled + for (const pb::ConfigValue& pb_config_value : pb_entry.flag_disabled_config_value()) { + const pb::Configuration& pb_config = pb_config_value.config(); + + ConfigDescription config; + if (!DeserializeConfigFromPb(pb_config, &config, out_error)) { + return false; + } + + FeatureFlagAttribute flag; + flag.name = pb_config_value.value().item().flag_name(); + flag.negated = pb_config_value.value().item().flag_negated(); + ResourceConfigValue* config_value = + entry->FindOrCreateFlagDisabledValue(std::move(flag), config, pb_config.product()); + if (config_value->value != nullptr) { + *out_error = "duplicate configuration in resource table"; + return false; + } + + config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config, + &out_table->string_pool, files, out_error); + if (config_value->value == nullptr) { return false; } @@ -615,6 +643,12 @@ bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file, out_file->source.path = pb_file.source_path(); out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type()); + out_file->flag_status = (FlagStatus)pb_file.flag_status(); + if (!pb_file.flag_name().empty()) { + out_file->flag = + FeatureFlagAttribute{.name = pb_file.flag_name(), .negated = pb_file.flag_negated()}; + } + std::string config_error; if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) { std::ostringstream error; @@ -748,7 +782,6 @@ std::unique_ptr DeserializeValueFromPb(const pb::Value& pb_value, if (value == nullptr) { return {}; } - } else if (pb_value.has_compound_value()) { const pb::CompoundValue& pb_compound_value = pb_value.compound_value(); switch (pb_compound_value.value_case()) { @@ -1018,6 +1051,12 @@ std::unique_ptr DeserializeItemFromPb(const pb::Item& pb_item, DeserializeItemFromPbInternal(pb_item, src_pool, config, value_pool, files, out_error); if (item) { item->SetFlagStatus((FlagStatus)pb_item.flag_status()); + if (!pb_item.flag_name().empty()) { + FeatureFlagAttribute flag; + flag.name = pb_item.flag_name(); + flag.negated = pb_item.flag_negated(); + item->SetFlag(std::move(flag)); + } } return item; } diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 5772b3b0b3e6d955d4b3235c308bc23a7383897c..d83fe916ee95507053c6c6094ded931f94be5a81 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -427,6 +427,14 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), source_pool.get()); } + + for (const ResourceConfigValue* config_value : entry.flag_disabled_values) { + pb::ConfigValue* pb_config_value = pb_entry->add_flag_disabled_config_value(); + SerializeConfig(config_value->config, pb_config_value->mutable_config()); + pb_config_value->mutable_config()->set_product(config_value->product); + SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), + source_pool.get()); + } } } } @@ -721,6 +729,11 @@ void SerializeValueToPb(const Value& value, pb::Value* out_value, android::Strin } if (out_value->has_item()) { out_value->mutable_item()->set_flag_status((uint32_t)value.GetFlagStatus()); + if (value.GetFlag()) { + const auto& flag = value.GetFlag(); + out_value->mutable_item()->set_flag_negated(flag->negated); + out_value->mutable_item()->set_flag_name(flag->name); + } } } @@ -730,6 +743,11 @@ void SerializeItemToPb(const Item& item, pb::Item* out_item) { item.Accept(&serializer); out_item->MergeFrom(value.item()); out_item->set_flag_status((uint32_t)item.GetFlagStatus()); + if (item.GetFlag()) { + const auto& flag = item.GetFlag(); + out_item->set_flag_negated(flag->negated); + out_item->set_flag_name(flag->name); + } } void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) { @@ -737,6 +755,11 @@ void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledF out_file->set_source_path(file.source.path); out_file->set_type(SerializeFileReferenceTypeToPb(file.type)); SerializeConfig(file.config, out_file->mutable_config()); + out_file->set_flag_status((uint32_t)file.flag_status); + if (file.flag) { + out_file->set_flag_negated(file.flag->negated); + out_file->set_flag_name(file.flag->name); + } for (const SourcedResourceName& exported : file.exported_symbols) { pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol(); diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp index c456e5c296d2676b825326819e4cf3fb0ffe83d6..7160b35033daf21e5d9f93f46bdf877be4f3edc4 100644 --- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp @@ -31,13 +31,29 @@ genrule { "res/values/ints.xml", "res/values/strings.xml", "res/layout/layout1.xml", + "res/layout/layout3.xml", + "res/flag(test.package.falseFlag)/values/bools.xml", + "res/flag(test.package.falseFlag)/layout/layout2.xml", + "res/flag(test.package.falseFlag)/drawable/removedpng.png", + "res/flag(test.package.trueFlag)/layout/layout3.xml", + "res/values/flag(test.package.trueFlag)/bools.xml", + "res/values/flag(!test.package.trueFlag)/bools.xml", + "res/values/flag(!test.package.falseFlag)/bools.xml", ], out: [ + "drawable_removedpng.(test.package.falseFlag).png.flat", "values_bools.arsc.flat", + "values_bools.(test.package.falseFlag).arsc.flat", + "values_bools.(test.package.trueFlag).arsc.flat", + "values_bools.(!test.package.falseFlag).arsc.flat", + "values_bools.(!test.package.trueFlag).arsc.flat", "values_bools2.arsc.flat", "values_ints.arsc.flat", "values_strings.arsc.flat", "layout_layout1.xml.flat", + "layout_layout2.(test.package.falseFlag).xml.flat", + "layout_layout3.xml.flat", + "layout_layout3.(test.package.trueFlag).xml.flat", ], cmd: "$(location aapt2) compile $(in) -o $(genDir) " + "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true", diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/drawable/removedpng.png b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/drawable/removedpng.png new file mode 100644 index 0000000000000000000000000000000000000000..8a9e6984be96ea93dcb63ae1973e530bdfb1eda0 Binary files /dev/null and b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/drawable/removedpng.png differ diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/layout/layout2.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/layout/layout2.xml new file mode 100644 index 0000000000000000000000000000000000000000..dec5de72925afddec7c7b028debe9a305ada890c --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/layout/layout2.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/values/bools.xml new file mode 100644 index 0000000000000000000000000000000000000000..c46c4d4d8546d4523dc791fe764396a7186376bb --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.falseFlag)/values/bools.xml @@ -0,0 +1,4 @@ + + + false + \ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.trueFlag)/layout/layout3.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.trueFlag)/layout/layout3.xml new file mode 100644 index 0000000000000000000000000000000000000000..5aeee0ee1e28c9ff6df68128f8e7113f3065ba9d --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag(test.package.trueFlag)/layout/layout3.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml new file mode 100644 index 0000000000000000000000000000000000000000..dec5de72925afddec7c7b028debe9a305ada890c --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml index 1ed0c8a5f1e60869ef165a5fc67efe72b26cfe3b..35975ed1274a67f0477d29380b33f7744cf2c83e 100644 --- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml @@ -9,4 +9,15 @@ false true + + false + true + + true + false + + true + false + true + false \ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.falseFlag)/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.falseFlag)/bools.xml new file mode 100644 index 0000000000000000000000000000000000000000..a63749c6ed7e6024cec1429e2135e33e0bdea61b --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.falseFlag)/bools.xml @@ -0,0 +1,4 @@ + + + true + \ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.trueFlag)/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.trueFlag)/bools.xml new file mode 100644 index 0000000000000000000000000000000000000000..bb5526e69f97c2173a7329389805a6b1c7f5b6ea --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(!test.package.trueFlag)/bools.xml @@ -0,0 +1,4 @@ + + + false + \ No newline at end of file diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(test.package.trueFlag)/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(test.package.trueFlag)/bools.xml new file mode 100644 index 0000000000000000000000000000000000000000..eba780e88c9a5631c4b6c39a94e814ef120be123 --- /dev/null +++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag(test.package.trueFlag)/bools.xml @@ -0,0 +1,4 @@ + + + true + \ No newline at end of file diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp index 3db37c2fa6f8398824341f85b3e6c2288fd2c3c5..629300838bbe578dc16095801847da5980393041 100644 --- a/tools/aapt2/link/FlaggedResources_test.cpp +++ b/tools/aapt2/link/FlaggedResources_test.cpp @@ -17,6 +17,7 @@ #include "LoadedApk.h" #include "cmd/Dump.h" #include "io/StringStream.h" +#include "test/Common.h" #include "test/Test.h" #include "text/Printer.h" @@ -75,6 +76,10 @@ TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) { std::string output; DumpResourceTableToString(loaded_apk.get(), &output); + ASSERT_EQ(output.find("bool4"), std::string::npos); + ASSERT_EQ(output.find("str1"), std::string::npos); + ASSERT_EQ(output.find("layout2"), std::string::npos); + ASSERT_EQ(output.find("removedpng"), std::string::npos); } TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) { @@ -86,6 +91,8 @@ TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) { ASSERT_EQ(output.find("bool4"), std::string::npos); ASSERT_EQ(output.find("str1"), std::string::npos); + ASSERT_EQ(output.find("layout2"), std::string::npos); + ASSERT_EQ(output.find("removedpng"), std::string::npos); } TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) { @@ -98,4 +105,47 @@ TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) { ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos); } +TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlag) { + test::TestDiagnosticsImpl diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + ASSERT_FALSE(CompileFile( + GetTestPath("res/values/values.xml"), + R"( + false + true + )", + compiled_files_dir, &diag, + {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"})); + ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool/bool1'")); +} + +TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlagDifferentFiles) { + test::TestDiagnosticsImpl diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + ASSERT_TRUE(CompileFile( + GetTestPath("res/values/values1.xml"), + R"( + false + )", + compiled_files_dir, &diag, + {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"})); + ASSERT_TRUE(CompileFile( + GetTestPath("res/values/values2.xml"), + R"( + true + )", + compiled_files_dir, &diag, + {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"})); + const std::string out_apk = GetTestPath("out.apk"); + std::vector link_args = { + "--manifest", + GetDefaultManifest(), + "-o", + out_apk, + }; + + ASSERT_FALSE(Link(link_args, compiled_files_dir, &diag)); + ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool1'")); +} + } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 37a039e9528f6aa0a4cb2f387dc82284b6fea96e..1bef5f8b17f60e6ea62c4c3bfb8187c77cee3e57 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -321,6 +321,30 @@ bool TableMerger::DoMerge(const android::Source& src, ResourceTablePackage* src_ } } } + + // disabled values + for (auto& src_config_value : src_entry->flag_disabled_values) { + auto dst_config_value = dst_entry->FindOrCreateFlagDisabledValue( + src_config_value->value->GetFlag().value(), src_config_value->config, + src_config_value->product); + if (!dst_config_value->value) { + // Resource does not exist, add it now. + // Must clone the value since it might be in the values vector as well + CloningValueTransformer cloner(&main_table_->string_pool); + dst_config_value->value = src_config_value->value->Transform(cloner); + } else { + error = true; + context_->GetDiagnostics()->Error( + android::DiagMessage(src_config_value->value->GetSource()) + << "duplicate value for resource '" << src_entry->name << "' " << "with config '" + << src_config_value->config << "' and flag '" + << (src_config_value->value->GetFlag()->negated ? "!" : "") + << src_config_value->value->GetFlag()->name << "'"); + context_->GetDiagnostics()->Note( + android::DiagMessage(dst_config_value->value->GetSource()) + << "resource previously defined here"); + } + } } } return !error; @@ -353,6 +377,8 @@ bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFi file_ref->SetSource(file_desc.source); file_ref->type = file_desc.type; file_ref->file = file; + file_ref->SetFlagStatus(file_desc.flag_status); + file_ref->SetFlag(file_desc.flag); ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package); pkg->FindOrCreateType(file_desc.name.type) diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp index cdf24534184451572cd91be7b7fd6cf678442260..c7dd4c90e67f22336679366c001b6ccedc0a5cd6 100644 --- a/tools/aapt2/test/Common.cpp +++ b/tools/aapt2/test/Common.cpp @@ -21,23 +21,6 @@ using android::ConfigDescription; namespace aapt { namespace test { -struct TestDiagnosticsImpl : public android::IDiagnostics { - void Log(Level level, android::DiagMessageActual& actual_msg) override { - switch (level) { - case Level::Note: - return; - - case Level::Warn: - std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl; - break; - - case Level::Error: - std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl; - break; - } - } -}; - android::IDiagnostics* GetDiagnostics() { static TestDiagnosticsImpl diag; return &diag; diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 04379804d8eef631caf5d9a1a8786423bf43f24f..b06c4329488e2943c00e08f81e7c0eba36d97b4b 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -37,6 +37,32 @@ namespace aapt { namespace test { +struct TestDiagnosticsImpl : public android::IDiagnostics { + void Log(Level level, android::DiagMessageActual& actual_msg) override { + switch (level) { + case Level::Note: + return; + + case Level::Warn: + std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl; + log << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl; + break; + + case Level::Error: + std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl; + log << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl; + break; + } + } + + std::string GetLog() { + return log.str(); + } + + private: + std::ostringstream log; +}; + android::IDiagnostics* GetDiagnostics(); inline ResourceName ParseNameOrDie(android::StringPiece str) { diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index b91abe572306dadafce0583549739558a71e64ac..570bcf16c92c7fcc68e2e1130707e8d8c043211e 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -91,10 +91,13 @@ void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& } bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents, - android::StringPiece out_dir, android::IDiagnostics* diag) { + android::StringPiece out_dir, android::IDiagnostics* diag, + const std::vector& additional_args) { WriteFile(path, contents); CHECK(file::mkdirs(out_dir.data())); - return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0; + std::vector args = {path, "-o", out_dir, "-v"}; + args.insert(args.end(), additional_args.begin(), additional_args.end()); + return CompileCommand(diag).Execute(args, &std::cerr) == 0; } bool CommandTestFixture::Link(const std::vector& args, android::IDiagnostics* diag) { diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h index 14298d1678f03fe1b4c4d577cb61d5ed7638e717..178d01156f32a3dd40408b3cc9554d6375e36a88 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -73,7 +73,8 @@ class CommandTestFixture : public TestDirectoryFixture { // Wries the contents of the file to the specified path. The file is compiled and the flattened // file is written to the out directory. bool CompileFile(const std::string& path, const std::string& contents, - android::StringPiece flat_out_dir, android::IDiagnostics* diag); + android::StringPiece flat_out_dir, android::IDiagnostics* diag, + const std::vector& additional_args = {}); // Executes the link command with the specified arguments. bool Link(const std::vector& args, android::IDiagnostics* diag); diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp index a9e63289ee93c2dc16c8f5fb5a2f1fe4cb66b6b2..590f7190881a7f76ca181d8534ae669b7d065619 100644 --- a/tools/systemfeatures/Android.bp +++ b/tools/systemfeatures/Android.bp @@ -30,8 +30,8 @@ genrule { name: "systemfeatures-gen-tests-srcs", cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true --feature-apis=WATCH > $(location RoNoFeatures.java) && " + - "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RwFeatures.java) && " + - "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)", + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: > $(location RwFeatures.java) && " + + "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)", out: [ "RwNoFeatures.java", "RoNoFeatures.java", diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt index 5df453deaf2a2b6be13d4d497565867bc8959afd..cba521e639cbfb0206976eeadd9d853ceda4bd87 100644 --- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt +++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt @@ -20,7 +20,10 @@ import com.google.common.base.CaseFormat import com.squareup.javapoet.ClassName import com.squareup.javapoet.JavaFile import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.ParameterizedTypeName import com.squareup.javapoet.TypeSpec +import java.util.HashMap +import java.util.Map import javax.lang.model.element.Modifier /* @@ -31,7 +34,7 @@ import javax.lang.model.element.Modifier * *

            *    com.foo.RoSystemFeatures --readonly=true \
          - *           --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348
          + *           --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348 --feature=PC:UNAVAILABLE
            *           --feature-apis=WATCH,PC,LEANBACK
            * 
          * @@ -43,12 +46,13 @@ import javax.lang.model.element.Modifier * @AssumeTrueForR8 * public static boolean hasFeatureWatch(Context context); * @AssumeFalseForR8 - * public static boolean hasFeatureAutomotive(Context context); + * public static boolean hasFeaturePc(Context context); * @AssumeTrueForR8 * public static boolean hasFeatureVulkan(Context context); - * public static boolean hasFeaturePc(Context context); + * public static boolean hasFeatureAutomotive(Context context); * public static boolean hasFeatureLeanback(Context context); * public static Boolean maybeHasFeature(String feature, int version); + * public static ArrayMap getCompileTimeAvailableFeatures(); * } * */ @@ -58,6 +62,7 @@ object SystemFeaturesGenerator { private const val READONLY_ARG = "--readonly=" private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager") private val CONTEXT_CLASS = ClassName.get("android.content", "Context") + private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo") private val ASSUME_TRUE_CLASS = ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8") private val ASSUME_FALSE_CLASS = @@ -67,7 +72,10 @@ object SystemFeaturesGenerator { println("Usage: SystemFeaturesGenerator [options]") println(" Options:") println(" --readonly=true|false Whether to encode features as build-time constants") - println(" --feature=\$NAME:\$VER A feature+version pair (blank version == disabled)") + println(" --feature=\$NAME:\$VER A feature+version pair, where \$VER can be:") + println(" * blank/empty == undefined (variable API)") + println(" * valid int == enabled (constant API)") + println(" * UNAVAILABLE == disabled (constant API)") println(" This will always generate associated query APIs,") println(" adding to or replacing those from `--feature-apis=`.") println(" --feature-apis=\$NAME_1,\$NAME_2") @@ -89,7 +97,7 @@ object SystemFeaturesGenerator { var readonly = false var outputClassName: ClassName? = null - val featureArgs = mutableListOf() + val featureArgs = mutableListOf() // We could just as easily hardcode this list, as the static API surface should change // somewhat infrequently, but this decouples the codegen from the framework completely. val featureApiArgs = mutableSetOf() @@ -122,7 +130,7 @@ object SystemFeaturesGenerator { featureArgs.associateByTo( features, { it.name }, - { FeatureInfo(it.name, it.version, readonly) }, + { FeatureInfo(it.name, it.version, it.readonly && readonly) }, ) outputClassName @@ -139,6 +147,7 @@ object SystemFeaturesGenerator { addFeatureMethodsToClass(classBuilder, features.values) addMaybeFeatureMethodToClass(classBuilder, features.values) + addGetFeaturesMethodToClass(classBuilder, features.values) // TODO(b/203143243): Add validation of build vs runtime values to ensure consistency. JavaFile.builder(outputClassName.packageName(), classBuilder.build()) @@ -154,13 +163,17 @@ object SystemFeaturesGenerator { * Parses a feature argument of the form "--feature=$NAME:$VER", where "$VER" is optional. * * "--feature=WATCH:0" -> Feature enabled w/ version 0 (default version when enabled) * * "--feature=WATCH:7" -> Feature enabled w/ version 7 - * * "--feature=WATCH:" -> Feature disabled + * * "--feature=WATCH:" -> Feature status undefined, runtime API generated + * * "--feature=WATCH:UNAVAILABLE" -> Feature disabled */ - private fun parseFeatureArg(arg: String): FeatureArg { + private fun parseFeatureArg(arg: String): FeatureInfo { val featureArgs = arg.substring(FEATURE_ARG.length).split(":") val name = parseFeatureName(featureArgs[0]) - val version = featureArgs.getOrNull(1)?.toIntOrNull() - return FeatureArg(name, version) + return when (featureArgs.getOrNull(1)) { + null, "" -> FeatureInfo(name, null, readonly = false) + "UNAVAILABLE" -> FeatureInfo(name, null, readonly = true) + else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true) + } } private fun parseFeatureName(name: String): String = @@ -218,7 +231,7 @@ object SystemFeaturesGenerator { /* * Adds a generic query method to the class with the form: {@code public static boolean * maybeHasFeature(String featureName, int version)}, returning null if the feature version is - * undefined or not readonly. + * undefined or not (compile-time) readonly. * * This method is useful for internal usage within the framework, e.g., from the implementation * of {@link android.content.pm.PackageManager#hasSystemFeature(Context)}, when we may only @@ -267,7 +280,41 @@ object SystemFeaturesGenerator { builder.addMethod(methodBuilder.build()) } - private data class FeatureArg(val name: String, val version: Int?) + /* + * Adds a method to get all compile-time enabled features. + * + * This method is useful for internal usage within the framework to augment + * any system features that are parsed from the various partitions. + */ + private fun addGetFeaturesMethodToClass( + builder: TypeSpec.Builder, + features: Collection, + ) { + val methodBuilder = + MethodSpec.methodBuilder("getCompileTimeAvailableFeatures") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addAnnotation(ClassName.get("android.annotation", "NonNull")) + .addJavadoc("Gets features marked as available at compile-time, keyed by name." + + "\n\n@hide") + .returns(ParameterizedTypeName.get( + ClassName.get(Map::class.java), + ClassName.get(String::class.java), + FEATUREINFO_CLASS)) + + val availableFeatures = features.filter { it.readonly && it.version != null } + methodBuilder.addStatement("Map features = new \$T<>(\$L)", + HashMap::class.java, availableFeatures.size) + if (!availableFeatures.isEmpty()) { + methodBuilder.addStatement("FeatureInfo fi = new FeatureInfo()") + } + for (feature in availableFeatures) { + methodBuilder.addStatement("fi.name = \$T.\$N", PACKAGEMANAGER_CLASS, feature.name) + methodBuilder.addStatement("fi.version = \$L", feature.version) + methodBuilder.addStatement("features.put(fi.name, new FeatureInfo(fi))") + } + methodBuilder.addStatement("return features") + builder.addMethod(methodBuilder.build()) + } private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean) } diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen index 724639b52d234bd289f92b745b315d0b497b789f..edbfc423754778663f910aad5d2ed648ef75fbf3 100644 --- a/tools/systemfeatures/tests/golden/RoFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen @@ -3,16 +3,20 @@ // --readonly=true \ // --feature=WATCH:1 \ // --feature=WIFI:0 \ -// --feature=VULKAN:-1 \ +// --feature=VULKAN:UNAVAILABLE \ // --feature=AUTO: \ // --feature-apis=WATCH,PC package com.android.systemfeatures; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import com.android.aconfig.annotations.AssumeFalseForR8; import com.android.aconfig.annotations.AssumeTrueForR8; +import java.util.HashMap; +import java.util.Map; /** * @hide @@ -62,9 +66,8 @@ public final class RoFeatures { * * @hide */ - @AssumeFalseForR8 public static boolean hasFeatureAuto(Context context) { - return false; + return hasFeatureFallback(context, PackageManager.FEATURE_AUTO); } private static boolean hasFeatureFallback(Context context, String featureName) { @@ -79,10 +82,27 @@ public final class RoFeatures { switch (featureName) { case PackageManager.FEATURE_WATCH: return 1 >= version; case PackageManager.FEATURE_WIFI: return 0 >= version; - case PackageManager.FEATURE_VULKAN: return -1 >= version; - case PackageManager.FEATURE_AUTO: return false; + case PackageManager.FEATURE_VULKAN: return false; default: break; } return null; } + + /** + * Gets features marked as available at compile-time, keyed by name. + * + * @hide + */ + @NonNull + public static Map getCompileTimeAvailableFeatures() { + Map features = new HashMap<>(2); + FeatureInfo fi = new FeatureInfo(); + fi.name = PackageManager.FEATURE_WATCH; + fi.version = 1; + features.put(fi.name, new FeatureInfo(fi)); + fi.name = PackageManager.FEATURE_WIFI; + fi.version = 0; + features.put(fi.name, new FeatureInfo(fi)); + return features; + } } diff --git a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen index 59c5b4e8fecb0e64ea507ddd84e6bc37fa294874..bf7a00679fa6c8f8c789208ae5f434be2d3ba422 100644 --- a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen @@ -4,9 +4,13 @@ // --feature-apis=WATCH package com.android.systemfeatures; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import java.util.HashMap; +import java.util.Map; /** * @hide @@ -32,4 +36,15 @@ public final class RoNoFeatures { public static Boolean maybeHasFeature(String featureName, int version) { return null; } + + /** + * Gets features marked as available at compile-time, keyed by name. + * + * @hide + */ + @NonNull + public static Map getCompileTimeAvailableFeatures() { + Map features = new HashMap<>(0); + return features; + } } diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen index 6f897591e48f5dd179af358ad87eed5d5d0297c0..b20b228f98149a1223b11ab8140e2d8ea9893877 100644 --- a/tools/systemfeatures/tests/golden/RwFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen @@ -3,13 +3,17 @@ // --readonly=false \ // --feature=WATCH:1 \ // --feature=WIFI:0 \ -// --feature=VULKAN:-1 \ +// --feature=VULKAN:UNAVAILABLE \ // --feature=AUTO: package com.android.systemfeatures; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import java.util.HashMap; +import java.util.Map; /** * @hide @@ -62,4 +66,15 @@ public final class RwFeatures { public static Boolean maybeHasFeature(String featureName, int version) { return null; } + + /** + * Gets features marked as available at compile-time, keyed by name. + * + * @hide + */ + @NonNull + public static Map getCompileTimeAvailableFeatures() { + Map features = new HashMap<>(0); + return features; + } } diff --git a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen index 2111d564f28d7623f86417a95c25f7a84c58ebe6..d91f5b62d8d44d073344be9845cfb38e35443c2a 100644 --- a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen @@ -3,8 +3,12 @@ // --readonly=false package com.android.systemfeatures; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.content.pm.FeatureInfo; +import java.util.HashMap; +import java.util.Map; /** * @hide @@ -21,4 +25,15 @@ public final class RwNoFeatures { public static Boolean maybeHasFeature(String featureName, int version) { return null; } + + /** + * Gets features marked as available at compile-time, keyed by name. + * + * @hide + */ + @NonNull + public static Map getCompileTimeAvailableFeatures() { + Map features = new HashMap<>(0); + return features; + } } diff --git a/tools/systemfeatures/tests/src/FeatureInfo.java b/tools/systemfeatures/tests/src/FeatureInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..9d57edc64ca564c5867e90e374b7d1937cebca2f --- /dev/null +++ b/tools/systemfeatures/tests/src/FeatureInfo.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +/** Stub for testing */ +public final class FeatureInfo { + public String name; + public int version; + + public FeatureInfo() {} + + public FeatureInfo(FeatureInfo orig) { + name = orig.name; + version = orig.version; + } +} diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java index 6dfd244a807b37d55ee845a012d4226d5e45ae40..39f8fc44fe23693b6018bbff437c82c8dd309119 100644 --- a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java +++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import org.junit.Before; @@ -36,6 +37,8 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Map; + @RunWith(JUnit4.class) public class SystemFeaturesGeneratorTest { @@ -57,6 +60,7 @@ public class SystemFeaturesGeneratorTest { assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); assertThat(RwNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + assertThat(RwNoFeatures.getCompileTimeAvailableFeatures()).isEmpty(); } @Test @@ -68,6 +72,7 @@ public class SystemFeaturesGeneratorTest { assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + assertThat(RoNoFeatures.getCompileTimeAvailableFeatures()).isEmpty(); // Also ensure we fall back to the PackageManager for feature APIs without an accompanying // versioned feature definition. @@ -101,6 +106,7 @@ public class SystemFeaturesGeneratorTest { assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); assertThat(RwFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); + assertThat(RwFeatures.getCompileTimeAvailableFeatures()).isEmpty(); } @Test @@ -110,10 +116,13 @@ public class SystemFeaturesGeneratorTest { assertThat(RoFeatures.hasFeatureWatch(mContext)).isTrue(); assertThat(RoFeatures.hasFeatureWifi(mContext)).isTrue(); assertThat(RoFeatures.hasFeatureVulkan(mContext)).isFalse(); - assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse(); verify(mPackageManager, never()).hasSystemFeature(anyString(), anyInt()); - // For defined feature types, conditional queries should reflect the build-time versions. + // For defined feature types, conditional queries should reflect either: + // * Enabled if the feature version is specified + // * Disabled if UNAVAILABLE is specified + // * Unknown if no version value is provided + // VERSION=1 assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, -1)).isTrue(); assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isTrue(); @@ -124,15 +133,19 @@ public class SystemFeaturesGeneratorTest { assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isTrue(); assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 100)).isFalse(); - // VERSION=-1 - assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isTrue(); + // VERSION=UNAVAILABLE + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isFalse(); assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isFalse(); assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 100)).isFalse(); - // DISABLED - assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isFalse(); - assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isFalse(); - assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isFalse(); + // VERSION= + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false); + assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse(); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(true); + assertThat(RoFeatures.hasFeatureAuto(mContext)).isTrue(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isNull(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); + assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isNull(); // For feature APIs without an associated feature definition, conditional queries should // report null, and explicit queries should report runtime-defined versions. @@ -148,5 +161,12 @@ public class SystemFeaturesGeneratorTest { assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", -1)).isNull(); assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull(); + assertThat(RoFeatures.maybeHasFeature("", 0)).isNull(); + + Map compiledFeatures = RoFeatures.getCompileTimeAvailableFeatures(); + assertThat(compiledFeatures.keySet()) + .containsExactly(PackageManager.FEATURE_WATCH, PackageManager.FEATURE_WIFI); + assertThat(compiledFeatures.get(PackageManager.FEATURE_WATCH).version).isEqualTo(1); + assertThat(compiledFeatures.get(PackageManager.FEATURE_WIFI).version).isEqualTo(0); } } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index fc4a909e761fb4a12ec11e1a360fbb5869b33bc7..f68ae2c7e2491e85b3c9410507779ec5891e00db 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -173,10 +173,6 @@ public class SharedConnectivityManager { } } } - - Executor getExecutor() { - return mExecutor; - } } private ISharedConnectivityService mService; @@ -192,7 +188,7 @@ public class SharedConnectivityManager { private final String mServicePackageName; private final String mIntentAction; private ServiceConnection mServiceConnection; - private final UserManager mUserManager; + private UserManager mUserManager; /** * Creates a new instance of {@link SharedConnectivityManager}. @@ -320,19 +316,15 @@ public class SharedConnectivityManager { private void registerCallbackInternal(SharedConnectivityClientCallback callback, SharedConnectivityCallbackProxy proxy) { - proxy.getExecutor().execute( - () -> { - try { - mService.registerCallback(proxy); - synchronized (mProxyDataLock) { - mProxyMap.put(callback, proxy); - } - } catch (RemoteException e) { - Log.e(TAG, "Exception in registerCallback", e); - callback.onRegisterCallbackFailed(e); - } - } - ); + try { + mService.registerCallback(proxy); + synchronized (mProxyDataLock) { + mProxyMap.put(callback, proxy); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception in registerCallback", e); + callback.onRegisterCallbackFailed(e); + } } /**