diff options
251 files changed, 6563 insertions, 2091 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 7e6c30f0719a..28462217e5de 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -75,6 +75,7 @@ aconfig_srcjars = [ ":com.android.internal.pm.pkg.component.flags-aconfig-java{.generated_srcjars}", ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}", ":com.android.media.flags.editing-aconfig-java{.generated_srcjars}", + ":com.android.media.flags.projection-aconfig-java{.generated_srcjars}", ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}", ":com.android.server.flags.services-aconfig-java{.generated_srcjars}", ":com.android.text.flags-aconfig-java{.generated_srcjars}", @@ -566,6 +567,21 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// MediaProjection +aconfig_declarations { + name: "com.android.media.flags.projection-aconfig", + package: "com.android.media.projection.flags", + srcs: [ + "media/java/android/media/flags/projection.aconfig", + ], +} + +java_aconfig_library { + name: "com.android.media.flags.projection-aconfig-java", + aconfig_declarations: "com.android.media.flags.projection-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Media TV aconfig_declarations { name: "android.media.tv.flags-aconfig", diff --git a/Ravenwood.bp b/Ravenwood.bp index 2babf6a56ab4..633702233cf4 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -93,16 +93,33 @@ java_library { ], } +// Jars in "ravenwood-runtime" are set to the classpath, sorted alphabetically. +// Rename some of the dependencies to make sure they're included in the intended order. +java_genrule { + name: "100-framework-minus-apex.ravenwood", + cmd: "cp $(in) $(out)", + srcs: [":framework-minus-apex.ravenwood"], + out: ["100-framework-minus-apex.ravenwood.jar"], + visibility: ["//visibility:private"], +} + +java_genrule { + // Use 200 to make sure it comes before the mainline stub ("all-updatable..."). + name: "200-kxml2-android", + cmd: "cp $(in) $(out)", + srcs: [":kxml2-android"], + out: ["200-kxml2-android.jar"], + visibility: ["//visibility:private"], +} + android_ravenwood_libgroup { name: "ravenwood-runtime", libs: [ - // Prefixed with "200" to ensure it's sorted early in Tradefed classpath - // so that we provide a concrete implementation before Mainline stubs + "100-framework-minus-apex.ravenwood", "200-kxml2-android", "all-updatable-modules-system-stubs", "android.test.mock.ravenwood", - "framework-minus-apex.ravenwood", - "hoststubgen-helper-framework-runtime.ravenwood", + "ravenwood-helper-runtime", "hoststubgen-helper-runtime.ravenwood", // Provide runtime versions of utils linked in below diff --git a/core/api/current.txt b/core/api/current.txt index 36063288b538..661caba77757 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5329,6 +5329,7 @@ package android.app { method public int getStartType(); method public int getStartupState(); method @NonNull public java.util.Map<java.lang.Integer,java.lang.Long> getStartupTimestamps(); + method @FlaggedApi("android.content.pm.stay_stopped") public boolean wasForceStopped(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.ApplicationStartInfo> CREATOR; field public static final int LAUNCH_MODE_SINGLE_INSTANCE = 2; // 0x2 @@ -7955,9 +7956,11 @@ package android.app.admin { field public static final String LOCK_TASK_POLICY = "lockTask"; field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended"; field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked"; + field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; field public static final String PERMISSION_GRANT_POLICY = "permissionGrant"; field public static final String PERSISTENT_PREFERRED_ACTIVITY_POLICY = "persistentPreferredActivity"; field public static final String RESET_PASSWORD_TOKEN_POLICY = "resetPasswordToken"; + field @FlaggedApi("android.app.admin.flags.security_log_v2_enabled") public static final String SECURITY_LOGGING_POLICY = "securityLogging"; field public static final String STATUS_BAR_DISABLED_POLICY = "statusBarDisabled"; field @FlaggedApi("android.app.admin.flags.policy_engine_migration_v2_enabled") public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; field public static final String USER_CONTROL_DISABLED_PACKAGES_POLICY = "userControlDisabledPackages"; @@ -8014,7 +8017,7 @@ package android.app.admin { method public CharSequence getDeviceOwnerLockScreenInfo(); method @Nullable public String getDevicePolicyManagementRoleHolderPackage(); method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName); - method @NonNull public String getEnrollmentSpecificId(); + method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @NonNull @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_CERTIFICATES, conditional=true) public String getEnrollmentSpecificId(); method @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET, conditional=true) public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName); method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName); method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName); @@ -8053,7 +8056,7 @@ package android.app.admin { method @Deprecated public int getPasswordMinimumSymbols(@Nullable android.content.ComponentName); method @Deprecated public int getPasswordMinimumUpperCase(@Nullable android.content.ComponentName); method @Deprecated public int getPasswordQuality(@Nullable android.content.ComponentName); - method @Nullable public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@NonNull android.content.ComponentName); + method @FlaggedApi("android.app.admin.flags.permission_migration_for_zero_trust_api_enabled") @Nullable @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional=true) public android.app.admin.SystemUpdateInfo getPendingSystemUpdate(@Nullable android.content.ComponentName); method @RequiresPermission(value=android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, conditional=true) public int getPermissionGrantState(@Nullable android.content.ComponentName, @NonNull String, @NonNull String); method public int getPermissionPolicy(android.content.ComponentName); method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName); @@ -12382,6 +12385,8 @@ package android.content.pm { method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle); method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle); method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles(); + method @FlaggedApi("android.app.admin.flags.allow_querying_profile_type") public boolean isManagedProfile(@NonNull android.os.UserHandle); + method @FlaggedApi("android.app.admin.flags.allow_querying_profile_type") public boolean isProfile(@NonNull android.os.UserHandle); method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity); method @RequiresPermission(anyOf={android.Manifest.permission.INTERACT_ACROSS_PROFILES, "android.permission.INTERACT_ACROSS_USERS"}) public void startActivity(@NonNull android.content.Intent, @NonNull android.os.UserHandle, @Nullable android.app.Activity, @Nullable android.os.Bundle); method public void startMainActivity(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); @@ -12472,8 +12477,11 @@ package android.content.pm { public class LauncherApps { method public java.util.List<android.content.pm.LauncherActivityInfo> getActivityList(String, android.os.UserHandle); method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getAllPackageInstallerSessions(); + method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public android.content.IntentSender getAppMarketActivityIntent(@Nullable String, @NonNull android.os.UserHandle); method public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException; + method @FlaggedApi("android.os.allow_private_profile") @Nullable @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public final android.content.pm.LauncherUserInfo getLauncherUserInfo(@NonNull android.os.UserHandle); method public android.content.pm.LauncherApps.PinItemRequest getPinItemRequest(android.content.Intent); + method @FlaggedApi("android.os.allow_private_profile") @NonNull @RequiresPermission(conditional=true, anyOf={"android.permission.ACCESS_HIDDEN_PROFILES_FULL", android.Manifest.permission.ACCESS_HIDDEN_PROFILES}) public java.util.List<java.lang.String> getPreInstalledSystemPackages(@NonNull android.os.UserHandle); method public java.util.List<android.os.UserHandle> getProfiles(); method public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable(android.content.pm.ShortcutInfo, int); method @Nullable public android.content.IntentSender getShortcutConfigActivityIntent(@NonNull android.content.pm.LauncherActivityInfo); @@ -12555,6 +12563,14 @@ package android.content.pm { field public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1024; // 0x400 } + @FlaggedApi("android.os.allow_private_profile") public final class LauncherUserInfo implements android.os.Parcelable { + method @FlaggedApi("android.os.allow_private_profile") public int describeContents(); + method @FlaggedApi("android.os.allow_private_profile") public int getUserSerialNumber(); + method @FlaggedApi("android.os.allow_private_profile") @NonNull public String getUserType(); + method @FlaggedApi("android.os.allow_private_profile") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("android.os.allow_private_profile") @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherUserInfo> CREATOR; + } + public final class ModuleInfo implements android.os.Parcelable { method public int describeContents(); method @Nullable public CharSequence getName(); @@ -19273,6 +19289,7 @@ package android.hardware.camera2 { method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions(); method public boolean isCaptureProcessProgressAvailable(int); method public boolean isPostviewAvailable(int); + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> EFV_PADDING_ZOOM_FACTOR_RANGE; field public static final int EXTENSION_AUTOMATIC = 0; // 0x0 field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1 field public static final int EXTENSION_BOKEH = 2; // 0x2 @@ -19874,6 +19891,32 @@ package android.hardware.camera2 { field public static final int MAX_THUMBNAIL_DIMENSION = 256; // 0x100 } + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureRequest { + ctor public ExtensionCaptureRequest(); + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> EFV_AUTO_ZOOM; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EFV_STABILIZATION_MODE; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; // 0x1 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_LOCKED = 2; // 0x2 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final int EFV_STABILIZATION_MODE_OFF = 0; // 0x0 + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; + } + + @FlaggedApi("com.android.internal.camera.flags.concert_mode") public final class ExtensionCaptureResult { + ctor public ExtensionCaptureResult(); + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> EFV_AUTO_ZOOM; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_MAX_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<int[]> EFV_PADDING_REGION; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_PADDING_ZOOM_FACTOR; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> EFV_ROTATE_VIEWPORT; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EFV_STABILIZATION_MODE; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES; + field @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.util.Pair<java.lang.Integer,java.lang.Integer>> EFV_TRANSLATE_VIEWPORT; + } + public class MultiResolutionImageReader implements java.lang.AutoCloseable { ctor public MultiResolutionImageReader(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int, @IntRange(from=1) int); method public void close(); @@ -22496,6 +22539,7 @@ package android.media { method @NonNull public static android.view.Surface createPersistentInputSurface(); method public int dequeueInputBuffer(long); method public int dequeueOutputBuffer(@NonNull android.media.MediaCodec.BufferInfo, long); + method @FlaggedApi("android.media.codec.null_output_surface") public void detachOutputSurface(); method protected void finalize(); method public void flush(); method @NonNull public String getCanonicalName(); @@ -22519,6 +22563,7 @@ package android.media { method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>); method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; + method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") public void queueSecureInputBuffers(int, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>, @NonNull java.util.ArrayDeque<android.media.MediaCodec.CryptoInfo>); method public void release(); method public void releaseOutputBuffer(int, boolean); method public void releaseOutputBuffer(int, long); @@ -22543,6 +22588,7 @@ package android.media { field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1 field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8 field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1 + field @FlaggedApi("android.media.codec.null_output_surface") public static final int CONFIGURE_FLAG_DETACHED_SURFACE = 8; // 0x8 field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1 field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2 field public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4; // 0x4 @@ -22694,6 +22740,7 @@ package android.media { method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int); method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int); method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long); + method @FlaggedApi("com.android.media.codec.flags.large_audio_frame") @NonNull public android.media.MediaCodec.QueueRequest setMultiFrameEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, @NonNull java.util.ArrayDeque<android.media.MediaCodec.BufferInfo>, @NonNull java.util.ArrayDeque<android.media.MediaCodec.CryptoInfo>); method @NonNull public android.media.MediaCodec.QueueRequest setPresentationTimeUs(long); method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String); } @@ -22788,6 +22835,7 @@ package android.media { field @Deprecated public static final int COLOR_QCOM_FormatYUV420SemiPlanar = 2141391872; // 0x7fa30c00 field @Deprecated public static final int COLOR_TI_FormatYUV420PackedSemiPlanar = 2130706688; // 0x7f000100 field public static final String FEATURE_AdaptivePlayback = "adaptive-playback"; + field @FlaggedApi("android.media.codec.null_output_surface") public static final String FEATURE_DetachedSurface = "detached-surface"; field @FlaggedApi("android.media.codec.dynamic_color_aspects") public static final String FEATURE_DynamicColorAspects = "dynamic-color-aspects"; field public static final String FEATURE_DynamicTimestamp = "dynamic-timestamp"; field public static final String FEATURE_EncodingStatistics = "encoding-statistics"; @@ -26915,6 +26963,67 @@ package android.media.tv { field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.SectionResponse> CREATOR; } + @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public final class SignalingDataInfo implements android.os.Parcelable { + ctor public SignalingDataInfo(@NonNull String, @NonNull String, int, int); + ctor public SignalingDataInfo(@NonNull String, @NonNull String, int, int, @NonNull String); + method public int describeContents(); + method @NonNull public String getEncoding(); + method public int getGroup(); + method @NonNull public String getSignalingDataType(); + method @NonNull public String getTable(); + method public int getVersion(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field public static final String CONTENT_ENCODING_BASE64 = "Base64"; + field public static final String CONTENT_ENCODING_UTF_8 = "UTF-8"; + field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.SignalingDataInfo> CREATOR; + field public static final int LLS_NO_GROUP_ID = -1; // 0xffffffff + } + + @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public final class SignalingDataRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable { + ctor public SignalingDataRequest(int, int, int, @NonNull java.util.List<java.lang.String>); + method public int getGroup(); + method @NonNull public java.util.List<java.lang.String> getSignalingDataTypes(); + field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.SignalingDataRequest> CREATOR; + field public static final int SIGNALING_DATA_NO_GROUP_ID = -1; // 0xffffffff + field public static final String SIGNALING_METADATA_AEAT = "AEAT"; + field public static final String SIGNALING_METADATA_AEI = "AEI"; + field public static final String SIGNALING_METADATA_APD = "APD"; + field public static final String SIGNALING_METADATA_ASD = "ASD"; + field public static final String SIGNALING_METADATA_ASPD = "ASPD"; + field public static final String SIGNALING_METADATA_CAD = "CAD"; + field public static final String SIGNALING_METADATA_CDT = "CDT"; + field public static final String SIGNALING_METADATA_CRIT = "CRIT"; + field public static final String SIGNALING_METADATA_DCIT = "DCIT"; + field public static final String SIGNALING_METADATA_DWD = "DWD"; + field public static final String SIGNALING_METADATA_EMSG = "EMSG"; + field public static final String SIGNALING_METADATA_EVTI = "EVTI"; + field public static final String SIGNALING_METADATA_HELD = "HELD"; + field public static final String SIGNALING_METADATA_IED = "IED"; + field public static final String SIGNALING_METADATA_MPD = "MPD"; + field public static final String SIGNALING_METADATA_MPIT = "MPIT"; + field public static final String SIGNALING_METADATA_MPT = "MPT"; + field public static final String SIGNALING_METADATA_OSN = "OSN"; + field public static final String SIGNALING_METADATA_PAT = "PAT"; + field public static final String SIGNALING_METADATA_RDT = "RDT"; + field public static final String SIGNALING_METADATA_RRT = "RRT"; + field public static final String SIGNALING_METADATA_RSAT = "RSAT"; + field public static final String SIGNALING_METADATA_SLT = "SLT"; + field public static final String SIGNALING_METADATA_SMT = "SMT"; + field public static final String SIGNALING_METADATA_SSD = "SSD"; + field public static final String SIGNALING_METADATA_STSID = "STSID"; + field public static final String SIGNALING_METADATA_STT = "STT"; + field public static final String SIGNALING_METADATA_USBD = "USBD"; + field public static final String SIGNALING_METADATA_USD = "USD"; + field public static final String SIGNALING_METADATA_VSPD = "VSPD"; + } + + @FlaggedApi("android.media.tv.flags.tiaf_v_apis") public final class SignalingDataResponse extends android.media.tv.BroadcastInfoResponse implements android.os.Parcelable { + ctor public SignalingDataResponse(int, int, int, @NonNull java.util.List<java.lang.String>, @NonNull java.util.List<android.media.tv.SignalingDataInfo>); + method @NonNull public java.util.List<android.media.tv.SignalingDataInfo> getSignalingDataInfoList(); + method @NonNull public java.util.List<java.lang.String> getSignalingDataTypes(); + field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.SignalingDataResponse> CREATOR; + } + public final class StreamEventRequest extends android.media.tv.BroadcastInfoRequest implements android.os.Parcelable { ctor public StreamEventRequest(int, int, @NonNull android.net.Uri, @NonNull String); method @NonNull public String getEventName(); @@ -33967,6 +34076,9 @@ package android.os { field public static final int USER_OPERATION_ERROR_MAX_USERS = 6; // 0x6 field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1 field public static final int USER_OPERATION_SUCCESS = 0; // 0x0 + field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; + field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; + field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE"; } public static class UserManager.UserOperationException extends java.lang.RuntimeException { @@ -37134,6 +37246,7 @@ package android.provider { field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS"; field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS"; field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS"; + field @FlaggedApi("android.app.app_restrictions_api") public static final String ACTION_BACKGROUND_RESTRICTIONS_SETTINGS = "android.settings.BACKGROUND_RESTRICTIONS_SETTINGS"; field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS"; field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL"; field public static final String ACTION_BLUETOOTH_SETTINGS = "android.settings.BLUETOOTH_SETTINGS"; @@ -39586,7 +39699,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityStart(); method @NonNull public String getKeystoreAlias(); method public int getMaxUsageCount(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -39594,7 +39707,7 @@ package android.security.keystore { method public boolean isDevicePropertiesAttestationIncluded(); method @NonNull public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isStrongBoxBacked(); method public boolean isUnlockedDeviceRequired(); @@ -39626,7 +39739,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); @@ -39731,14 +39844,14 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityForOriginationEnd(); method @Nullable public java.util.Date getKeyValidityStart(); method public int getMaxUsageCount(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); @@ -39760,7 +39873,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int); - method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); + method @FlaggedApi("android.security.mgf1_digest_setter_v2") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); @@ -50447,7 +50560,7 @@ package android.view { method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction); method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl); method public default int getBufferTransformHint(); - method @FlaggedApi("com.android.window.flags.get_host_token_api") @Nullable public default android.os.IBinder getHostToken(); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.window.InputTransferToken getInputTransferToken(); method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener); method public default void setChildBoundingInsets(@NonNull android.graphics.Rect); method public default void setTouchableRegion(@Nullable android.graphics.Region); @@ -52157,6 +52270,7 @@ package android.view { public class SurfaceControlViewHost { ctor public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.os.IBinder); + ctor @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public SurfaceControlViewHost(@NonNull android.content.Context, @NonNull android.view.Display, @Nullable android.window.InputTransferToken); method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage getSurfacePackage(); method @Nullable public android.view.View getView(); method public void relayout(int, int); @@ -52358,7 +52472,7 @@ package android.view { method public final void cancelPendingInputEvents(); method public boolean checkInputConnectionProxy(android.view.View); method public void clearAnimation(); - method @FlaggedApi("autofill_credman_dev_integration") public void clearCredentialManagerRequest(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest(); method public void clearFocus(); method public void clearViewTranslationCallback(); method public static int combineMeasuredStates(int, int); @@ -52468,8 +52582,8 @@ package android.view { method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final int getContentSensitivity(); method @UiContext public final android.content.Context getContext(); method protected android.view.ContextMenu.ContextMenuInfo getContextMenuInfo(); - method @FlaggedApi("autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback(); - method @FlaggedApi("autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getCredentialManagerRequest(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public final android.credentials.GetCredentialRequest getCredentialManagerRequest(); method public final boolean getDefaultFocusHighlightEnabled(); method public static int getDefaultSize(int, int); method public android.view.Display getDisplay(); @@ -52854,7 +52968,7 @@ package android.view { method public void setContentDescription(CharSequence); method @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public final void setContentSensitivity(int); method public void setContextClickable(boolean); - method @FlaggedApi("autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); method public void setDefaultFocusHighlightEnabled(boolean); method @Deprecated public void setDrawingCacheBackgroundColor(@ColorInt int); method @Deprecated public void setDrawingCacheEnabled(boolean); @@ -53733,11 +53847,11 @@ package android.view { method public abstract int addChildCount(int); method public abstract void asyncCommit(); method public abstract android.view.ViewStructure asyncNewChild(int); - method @FlaggedApi("autofill_credman_dev_integration") public void clearCredentialManagerRequest(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void clearCredentialManagerRequest(); method @Nullable public abstract android.view.autofill.AutofillId getAutofillId(); method public abstract int getChildCount(); - method @FlaggedApi("autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback(); - method @FlaggedApi("autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getCredentialManagerRequest(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException> getCredentialManagerCallback(); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") @Nullable public android.credentials.GetCredentialRequest getCredentialManagerRequest(); method public abstract android.os.Bundle getExtras(); method public abstract CharSequence getHint(); method public abstract CharSequence getText(); @@ -53762,7 +53876,7 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(CharSequence); method public abstract void setContextClickable(boolean); - method @FlaggedApi("autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); + method @FlaggedApi("android.service.autofill.autofill_credman_dev_integration") public void setCredentialManagerRequest(@NonNull android.credentials.GetCredentialRequest, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>); method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); @@ -54303,9 +54417,9 @@ package android.view { method @Deprecated public android.view.Display getDefaultDisplay(); method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics(); method public default boolean isCrossWindowBlurEnabled(); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.window.InputTransferToken, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); - method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); + method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.window.InputTransferToken, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver); method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer); method @FlaggedApi("com.android.window.flags.screen_recording_callbacks") @RequiresPermission(android.Manifest.permission.DETECT_SCREEN_RECORDING) public default void removeScreenRecordingCallback(@NonNull java.util.function.Consumer<java.lang.Integer>); @@ -55136,6 +55250,7 @@ package android.view.accessibility { field public static final int TYPE_MAGNIFICATION_OVERLAY = 6; // 0x6 field public static final int TYPE_SPLIT_SCREEN_DIVIDER = 5; // 0x5 field public static final int TYPE_SYSTEM = 3; // 0x3 + field @FlaggedApi("android.view.accessibility.add_type_window_control") public static final int TYPE_WINDOW_CONTROL = 7; // 0x7 } public class CaptioningManager { @@ -61286,6 +61401,12 @@ package android.window { field public static final int EDGE_RIGHT = 1; // 0x1 } + @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public final class InputTransferToken implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.window.InputTransferToken> CREATOR; + } + public interface OnBackAnimationCallback extends android.window.OnBackInvokedCallback { method public default void onBackCancelled(); method public default void onBackProgressed(@NonNull android.window.BackEvent); @@ -61333,11 +61454,11 @@ package android.window { @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable { ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents(); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public float getMinAlpha(); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public float getMinFractionRendered(); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @IntRange(from=1) public int getStabilityRequirementMillis(); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public void writeToParcel(@NonNull android.os.Parcel, int); field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @NonNull public static final android.os.Parcelable.Creator<android.window.TrustedPresentationThresholds> CREATOR; - field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minAlpha; - field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minFractionRendered; - field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @IntRange(from=1) public final int stabilityRequirementMs; } } diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt index b36b963f99c9..9b8ab9bbb8e9 100644 --- a/core/api/lint-baseline.txt +++ b/core/api/lint-baseline.txt @@ -245,6 +245,14 @@ BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SE Field 'ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE' is missing @BroadcastBehavior +CompileTimeConstant: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL: + All constants must be defined at compile time: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL +CompileTimeConstant: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED: + All constants must be defined at compile time: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED +CompileTimeConstant: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF: + All constants must be defined at compile time: android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF + + DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): Method android.accounts.AccountManager.newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match DeprecationMismatch: android.app.Activity#enterPictureInPictureMode(): @@ -1087,6 +1095,14 @@ RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean): Method 'setGeolocationEnabled' documentation mentions permissions without declaring @RequiresPermission +StaticUtils: ExtensionCaptureRequest: + Fully-static utility classes must not have constructor +StaticUtils: android.hardware.camera2.ExtensionCaptureRequest: + Fully-static utility classes must not have constructor +StaticUtils: android.hardware.camera2.ExtensionCaptureResult: + Fully-static utility classes must not have constructor + + Todo: android.hardware.camera2.params.StreamConfigurationMap: Documentation mentions 'TODO' Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor): @@ -1445,6 +1461,14 @@ UnflaggedApi: android.graphics.text.PositionedGlyphs#getItalicOverride(int): New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getItalicOverride(int) UnflaggedApi: android.graphics.text.PositionedGlyphs#getWeightOverride(int): New API must be flagged with @FlaggedApi: method android.graphics.text.PositionedGlyphs.getWeightOverride(int) +UnflaggedApi: android.hardware.camera2.ExtensionCaptureRequest: + New API must be flagged with @FlaggedApi: class android.hardware.camera2.ExtensionCaptureRequest +UnflaggedApi: android.hardware.camera2.ExtensionCaptureRequest#ExtensionCaptureRequest(): + New API must be flagged with @FlaggedApi: constructor android.hardware.camera2.ExtensionCaptureRequest() +UnflaggedApi: android.hardware.camera2.ExtensionCaptureResult: + New API must be flagged with @FlaggedApi: class android.hardware.camera2.ExtensionCaptureResult +UnflaggedApi: android.hardware.camera2.ExtensionCaptureResult#ExtensionCaptureResult(): + New API must be flagged with @FlaggedApi: constructor android.hardware.camera2.ExtensionCaptureResult() UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_CAR: New API must be flagged with @FlaggedApi: field android.media.MediaRoute2Info.TYPE_REMOTE_CAR UnflaggedApi: android.media.MediaRoute2Info#TYPE_REMOTE_COMPUTER: diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 97995fe8be08..20a621ba8bf2 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -100,6 +100,7 @@ package android { field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED"; field @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission") public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER"; field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER"; + field @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static final String CAMERA_PRIVACY_ALLOWLIST = "android.permission.CAMERA_PRIVACY_ALLOWLIST"; field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD"; field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD"; field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT"; @@ -202,6 +203,7 @@ package android { field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; field public static final String MANAGE_LOW_POWER_STANDBY = "android.permission.MANAGE_LOW_POWER_STANDBY"; + field @FlaggedApi("com.android.media.flags.limit_manage_media_projection") public static final String MANAGE_MEDIA_PROJECTION = "android.permission.MANAGE_MEDIA_PROJECTION"; field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION"; field public static final String MANAGE_NOTIFICATION_LISTENERS = "android.permission.MANAGE_NOTIFICATION_LISTENERS"; field public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS = "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS"; @@ -655,6 +657,7 @@ package android.app { field public static final String OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android:capture_consentless_bugreport_on_userdebug_build"; field public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state"; field @FlaggedApi("android.view.contentprotection.flags.create_accessibility_overlay_app_op_enabled") public static final String OPSTR_CREATE_ACCESSIBILITY_OVERLAY = "android:create_accessibility_overlay"; + field @FlaggedApi("android.location.flags.location_bypass") public static final String OPSTR_EMERGENCY_LOCATION = "android:emergency_location"; field @FlaggedApi("android.permission.flags.op_enable_mobile_data_by_user") public static final String OPSTR_ENABLE_MOBILE_DATA_BY_USER = "android:enable_mobile_data_by_user"; field public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager"; field public static final String OPSTR_ESTABLISH_VPN_SERVICE = "android:establish_vpn_service"; @@ -1306,6 +1309,7 @@ package android.app.admin { method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public String getFinancedDeviceKioskRoleHolder(); + method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public int getMaxPolicyStorageLimit(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle); @@ -1328,6 +1332,7 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS) public void setApplicationExemptions(@NonNull String, @NonNull java.util.Set<java.lang.Integer>) throws android.content.pm.PackageManager.NameNotFoundException; method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied(); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); + method @FlaggedApi("android.app.admin.flags.device_policy_size_tracking_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); @@ -3190,6 +3195,8 @@ package android.app.wearable { method @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideDataStream(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void provideWearableConnection(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void registerDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void startHotwordRecognition(@Nullable android.content.ComponentName, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void stopHotwordRecognition(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @FlaggedApi("android.app.wearable.enable_data_request_observer_api") @RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) public void unregisterDataRequestObserver(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); field public static final int STATUS_ACCESS_DENIED = 5; // 0x5 field @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") public static final int STATUS_CHANNEL_ERROR = 7; // 0x7 @@ -3676,6 +3683,7 @@ package android.content { field public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS"; field @Deprecated public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION"; field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA"; + field @FlaggedApi("android.security.frp_enforcement") public static final String ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED = "android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED"; field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION"; field @Deprecated public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS"; field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP"; @@ -4495,6 +4503,7 @@ package android.credentials.selection { ctor public FailureResult(int, @Nullable String); method public int getErrorCode(); method @Nullable public String getErrorMessage(); + method public static void sendFailureResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.FailureResult); field public static final int ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 2; // 0x2 field public static final int ERROR_CODE_DIALOG_CANCELED_BY_USER = 1; // 0x1 field public static final int ERROR_CODE_UI_FAILURE = 0; // 0x0 @@ -4556,17 +4565,13 @@ package android.credentials.selection { @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class RequestToken { } - @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class ResultHelper { - method public static void sendFailureResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.FailureResult); - method public static void sendUserSelectionResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.UserSelectionResult); - } - @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public final class UserSelectionResult { ctor public UserSelectionResult(@NonNull String, @NonNull String, @NonNull String, @Nullable android.credentials.selection.ProviderPendingIntentResponse); method @NonNull public String getEntryKey(); method @NonNull public String getEntrySubkey(); method @Nullable public android.credentials.selection.ProviderPendingIntentResponse getPendingIntentProviderResponse(); method @NonNull public String getProviderId(); + method public static void sendUserSelectionResult(@NonNull android.os.ResultReceiver, @NonNull android.credentials.selection.UserSelectionResult); } } @@ -4672,11 +4677,15 @@ package android.hardware { method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist(); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int, int); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int); } public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { @@ -4686,6 +4695,7 @@ package android.hardware { public static class SensorPrivacyManager.OnSensorPrivacyChangedListener.SensorPrivacyChangedParams { method public int getSensor(); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public int getState(); method public int getToggleType(); method public boolean isEnabled(); } @@ -10432,9 +10442,11 @@ package android.os { method public int getFlags(); method public int getMode(); field public static final int BUGREPORT_FLAG_DEFER_CONSENT = 2; // 0x2 + field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 4; // 0x4 field public static final int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 1; // 0x1 field public static final int BUGREPORT_MODE_FULL = 0; // 0x0 field public static final int BUGREPORT_MODE_INTERACTIVE = 1; // 0x1 + field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7 field public static final int BUGREPORT_MODE_REMOTE = 2; // 0x2 field public static final int BUGREPORT_MODE_TELEPHONY = 4; // 0x4 field public static final int BUGREPORT_MODE_WEAR = 3; // 0x3 @@ -11084,9 +11096,6 @@ package android.os { field public static final String USER_TYPE_FULL_GUEST = "android.os.usertype.full.GUEST"; field public static final String USER_TYPE_FULL_SECONDARY = "android.os.usertype.full.SECONDARY"; field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM"; - field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; - field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; - field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE"; field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS"; } @@ -13190,6 +13199,7 @@ package android.service.voice { method @Nullable public android.service.voice.HotwordDetectedResult getHotwordDetectedResult(); method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphraseRecognitionExtras(); method @Deprecated @Nullable public byte[] getTriggerAudio(); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") public boolean isRecognitionStopped(); field public static final int DATA_FORMAT_RAW = 0; // 0x0 field public static final int DATA_FORMAT_TRIGGER_AUDIO = 1; // 0x1 } @@ -13296,6 +13306,7 @@ package android.service.voice { method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer); field @Deprecated public static final int INITIALIZATION_STATUS_SUCCESS = 0; // 0x0 field @Deprecated public static final int INITIALIZATION_STATUS_UNKNOWN = 100; // 0x64 + field @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") public static final String KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK = "android.service.voice.HotwordDetectionService.KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK"; field public static final String SERVICE_INTERFACE = "android.service.voice.HotwordDetectionService"; } @@ -13509,7 +13520,6 @@ package android.service.voice { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.HotwordDetector.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES) public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.VisualQueryDetector createVisualQueryDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VisualQueryDetector.Callback); - method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setShouldReceiveSandboxedTrainingData(boolean); } } @@ -13581,7 +13591,11 @@ package android.service.wearable { method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>); method @FlaggedApi("android.app.wearable.enable_provide_wearable_connection_api") @BinderThread public void onSecureWearableConnectionProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @BinderThread public void onStartHotwordRecognition(@NonNull java.util.function.Consumer<android.service.voice.HotwordAudioStream>, @NonNull java.util.function.Consumer<java.lang.Integer>); method public abstract void onStopDetection(@NonNull String); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @BinderThread public void onStopHotwordAudioStream(); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @BinderThread public void onStopHotwordRecognition(@NonNull java.util.function.Consumer<java.lang.Integer>); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @BinderThread public void onValidatedByHotwordDetectionService(); field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index bb98770eafee..f6366a2afbf0 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1519,6 +1519,7 @@ package android.hardware { public final class SensorPrivacyManager { method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean); + method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int); } public static class SensorPrivacyManager.Sources { @@ -1840,6 +1841,14 @@ package android.hardware.soundtrigger { } +package android.hardware.usb { + + public final class UsbPort { + method @FlaggedApi("android.hardware.usb.flags.enable_is_mode_change_supported_api") @RequiresPermission(android.Manifest.permission.MANAGE_USB) public boolean isModeChangeSupported(); + } + +} + package android.inputmethodservice { public abstract class AbstractInputMethodService extends android.window.WindowProviderService implements android.view.KeyEvent.Callback { @@ -2269,9 +2278,7 @@ package android.os { } public final class BugreportParams { - field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 4; // 0x4 field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7 - field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7 } public class Build { @@ -3164,6 +3171,7 @@ package android.service.voice { method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setDataFormat(int); method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setHalEventReceivedMillis(long); method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setHotwordDetectedResult(@NonNull android.service.voice.HotwordDetectedResult); + method @FlaggedApi("android.app.wearable.enable_hotword_wearable_sensing_api") @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setIsRecognitionStopped(boolean); method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>); } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ab9a4ecd8506..23fe731701b6 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3927,6 +3927,7 @@ public class Activity extends ContextThemeWrapper if (keyCode == KeyEvent.KEYCODE_ESCAPE && mWindow.shouldCloseOnTouchOutside()) { event.startTracking(); + finish(); return true; } @@ -4027,10 +4028,7 @@ public class Activity extends ContextThemeWrapper } if (keyCode == KeyEvent.KEYCODE_ESCAPE - && mWindow.shouldCloseOnTouchOutside() - && event.isTracking() - && !event.isCanceled()) { - finish(); + && event.isTracking()) { return true; } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 5074ed706ad1..2100425a6771 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,6 +16,7 @@ package android.app; +import static android.location.flags.Flags.FLAG_LOCATION_BYPASS; import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER; import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED; import static android.view.contentprotection.flags.Flags.FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED; @@ -1571,9 +1572,17 @@ public class AppOpsManager { public static final int OP_UNARCHIVAL_CONFIRMATION = AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION; + /** + * Allows an app to access location without the traditional location permissions and while the + * user location setting is off, but only during pre-defined emergency sessions. + * + * @hide + */ + public static final int OP_EMERGENCY_LOCATION = AppProtoEnums.APP_OP_EMERGENCY_LOCATION; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 147; + public static final int _NUM_OP = 148; /** * All app ops represented as strings. @@ -1726,6 +1735,7 @@ public class AppOpsManager { OPSTR_RUN_BACKUP_JOBS, OPSTR_ARCHIVE_ICON_OVERLAY, OPSTR_UNARCHIVAL_CONFIRMATION, + OPSTR_EMERGENCY_LOCATION, }) public @interface AppOpString {} @@ -2439,6 +2449,16 @@ public class AppOpsManager { */ public static final String OPSTR_RUN_BACKUP_JOBS = "android:run_backup_jobs"; + /** + * Allows an app to access location without the traditional location permissions and while the + * user location setting is off, but only during pre-defined emergency sessions. + * + * @hide + */ + @SystemApi + @FlaggedApi(FLAG_LOCATION_BYPASS) + public static final String OPSTR_EMERGENCY_LOCATION = "android:emergency_location"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -3019,6 +3039,9 @@ public class AppOpsManager { new AppOpInfo.Builder(OP_UNARCHIVAL_CONFIRMATION, OPSTR_UNARCHIVAL_CONFIRMATION, "UNARCHIVAL_CONFIRMATION") .setDefaultMode(MODE_ALLOWED).build(), + // TODO(b/301150056): STOPSHIP determine how this appop should work with the permission + new AppOpInfo.Builder(OP_EMERGENCY_LOCATION, OPSTR_EMERGENCY_LOCATION, "EMERGENCY_LOCATION") + .setPermission(Manifest.permission.LOCATION_BYPASS).build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java index c6712c044539..3715c6e633dc 100644 --- a/core/java/android/app/ApplicationStartInfo.java +++ b/core/java/android/app/ApplicationStartInfo.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.icu.text.SimpleDateFormat; import android.os.Parcel; import android.os.Parcelable; @@ -249,7 +250,7 @@ public final class ApplicationStartInfo implements Parcelable { private @StartType int mStartType; /** - * @see #getStartIntent + * @see #getIntent */ private Intent mStartIntent; @@ -259,6 +260,11 @@ public final class ApplicationStartInfo implements Parcelable { private @LaunchMode int mLaunchMode; /** + * @see #wasForceStopped() + */ + private boolean mWasForceStopped; + + /** * @hide * */ @IntDef( @@ -427,6 +433,15 @@ public final class ApplicationStartInfo implements Parcelable { } /** + * @see #wasForceStopped() + * @param wasForceStopped whether the app had been force-stopped in the past + * @hide + */ + public void setForceStopped(boolean wasForceStopped) { + mWasForceStopped = wasForceStopped; + } + + /** * Current state of startup. * * Can be used to determine whether the object will have additional fields added as it may be @@ -578,6 +593,20 @@ public final class ApplicationStartInfo implements Parcelable { return mLaunchMode; } + /** + * Informs whether this is the first process launch for an app since it was + * {@link ApplicationInfo#FLAG_STOPPED force-stopped} for some reason. + * This allows the app to know if it should re-register for any alarms, jobs and other callbacks + * that were cleared when the app was force-stopped. + * + * @return {@code true} if this is the first process launch of the app after having been + * stopped, {@code false} otherwise. + */ + @FlaggedApi(android.content.pm.Flags.FLAG_STAY_STOPPED) + public boolean wasForceStopped() { + return mWasForceStopped; + } + @Override public int describeContents() { return 0; @@ -603,6 +632,7 @@ public final class ApplicationStartInfo implements Parcelable { dest.writeInt(mStartType); dest.writeParcelable(mStartIntent, flags); dest.writeInt(mLaunchMode); + dest.writeBoolean(mWasForceStopped); } /** @hide */ @@ -622,6 +652,7 @@ public final class ApplicationStartInfo implements Parcelable { mStartType = other.mStartType; mStartIntent = other.mStartIntent; mLaunchMode = other.mLaunchMode; + mWasForceStopped = other.mWasForceStopped; } private ApplicationStartInfo(@NonNull Parcel in) { @@ -643,6 +674,7 @@ public final class ApplicationStartInfo implements Parcelable { mStartIntent = in.readParcelable(Intent.class.getClassLoader(), android.content.Intent.class); mLaunchMode = in.readInt(); + mWasForceStopped = in.readBoolean(); } private static String intern(@Nullable String source) { @@ -720,6 +752,7 @@ public final class ApplicationStartInfo implements Parcelable { intentOut.close(); } proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode); + proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped); proto.end(token); } @@ -799,6 +832,10 @@ public final class ApplicationStartInfo implements Parcelable { case (int) ApplicationStartInfoProto.LAUNCH_MODE: mLaunchMode = proto.readInt(ApplicationStartInfoProto.LAUNCH_MODE); break; + case (int) ApplicationStartInfoProto.WAS_FORCE_STOPPED: + mWasForceStopped = proto.readBoolean( + ApplicationStartInfoProto.WAS_FORCE_STOPPED); + break; } } proto.end(token); @@ -823,6 +860,7 @@ 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'); if (mStartIntent != null) { sb.append(" intent=").append(mStartIntent.toString()) @@ -878,7 +916,7 @@ public final class ApplicationStartInfo implements Parcelable { && mDefiningUid == o.mDefiningUid && mReason == o.mReason && mStartupState == o.mStartupState && mStartType == o.mStartType && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName) - && timestampsEquals(o); + && timestampsEquals(o) && mWasForceStopped == o.mWasForceStopped; } @Override diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index d0d76a4c8285..0e201384812d 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -672,7 +672,16 @@ public class Dialog implements DialogInterface, Window.Callback, */ @Override public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + event.startTracking(); + return true; + } + if (keyCode == KeyEvent.KEYCODE_ESCAPE) { + if (mCancelable) { + cancel(); + } else { + dismiss(); + } event.startTracking(); return true; } @@ -712,11 +721,6 @@ public class Dialog implements DialogInterface, Window.Callback, } break; case KeyEvent.KEYCODE_ESCAPE: - if (mCancelable) { - cancel(); - } else { - dismiss(); - } return true; } } diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index d7aafa010e1d..a884ab0c4099 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -16,6 +16,8 @@ package android.app.admin; +import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; + import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.TestApi; @@ -45,6 +47,12 @@ public final class DevicePolicyIdentifiers { public static final String PERMISSION_GRANT_POLICY = "permissionGrant"; /** + * String identifier for {@link DevicePolicyManager#setSecurityLoggingEnabled}. + */ + @FlaggedApi(FLAG_SECURITY_LOG_V2_ENABLED) + public static final String SECURITY_LOGGING_POLICY = "securityLogging"; + + /** * String identifier for {@link DevicePolicyManager#setLockTaskPackages}. */ public static final String LOCK_TASK_POLICY = "lockTask"; @@ -174,6 +182,12 @@ public final class DevicePolicyIdentifiers { public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; /** + * String identifier for {@link DevicePolicyManager#setRequiredPasswordComplexity}. + */ + @FlaggedApi(Flags.FLAG_POLICY_ENGINE_MIGRATION_V2_ENABLED) + public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity"; + + /** * @hide */ public static final String USER_RESTRICTION_PREFIX = "userRestriction_"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9d50810425c6..34fb754b745f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -37,6 +37,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MTE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PACKAGE_STATE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE; @@ -52,6 +53,7 @@ import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; import static android.Manifest.permission.SET_TIME_ZONE; import static android.app.admin.flags.Flags.FLAG_ESIM_MANAGEMENT_ENABLED; +import static android.app.admin.flags.Flags.FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; @@ -13415,17 +13417,25 @@ public class DevicePolicyManager { } /** - * Called by device or profile owners to get information about a pending system update. + * Get information about a pending system update. + * + * Can be called by device or profile owners, and starting from Android + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, holders of the permission + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}. * * @param admin Which profile or device owner this request is associated with. * @return Information about a pending system update or {@code null} if no update pending. - * @throws SecurityException if {@code admin} is not a device or profile owner. + * @throws SecurityException if {@code admin} is not a device, profile owner or holders of + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}. * @see DeviceAdminReceiver#onSystemUpdatePending(Context, Intent, long) */ - public @Nullable SystemUpdateInfo getPendingSystemUpdate(@NonNull ComponentName admin) { + @RequiresPermission(value = MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES, conditional = true) + @SuppressLint("RequiresPermission") + @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED) + public @Nullable SystemUpdateInfo getPendingSystemUpdate(@Nullable ComponentName admin) { throwIfParentInstance("getPendingSystemUpdate"); try { - return mService.getPendingSystemUpdate(admin); + return mService.getPendingSystemUpdate(admin, mContext.getPackageName()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -13976,6 +13986,24 @@ public class DevicePolicyManager { * privacy-sensitive events happening outside the managed profile would have been redacted * already. * + * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the security logging + * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, + * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was + * successfully set or not. This callback will contain: + * <ul> + * <li> The policy identifier {@link DevicePolicyIdentifiers#SECURITY_LOGGING_POLICY} + * <li> The {@link TargetUser} that this policy relates to + * <li> The {@link PolicyUpdateResult}, which will be + * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the + * reason the policy failed to be set + * e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY}) + * </ul> + * If there has been a change to the policy, + * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser, + * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the + * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult} + * will contain the reason why the policy changed. + * * @param admin Which device admin this request is associated with, or {@code null} * if called by a delegated app. * @param enabled whether security logging should be enabled or not. @@ -16476,8 +16504,9 @@ public class DevicePolicyManager { * The identifier would be consistent even if the work profile is removed and enrolled again * (to the same organization), or the device is factory reset and re-enrolled. * - * Can only be called by the Profile Owner or Device Owner, if the - * {@link #setOrganizationId(String)} was previously called. + * Can only be called by the Profile Owner and Device Owner, and starting from Android + * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, holders of the permission + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES}. * If {@link #setOrganizationId(String)} was not called, then the returned value will be an * empty string. * @@ -16490,8 +16519,12 @@ public class DevicePolicyManager { * and must switch to using this method. * * @return A stable, enrollment-specific identifier. - * @throws SecurityException if the caller is not a profile owner or device owner. + * @throws SecurityException if the caller is not a profile owner, device owner or holding the + * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission */ + @RequiresPermission(value = MANAGE_DEVICE_POLICY_CERTIFICATES, conditional = true) + @SuppressLint("RequiresPermission") + @FlaggedApi(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_API_ENABLED) @NonNull public String getEnrollmentSpecificId() { throwIfParentInstance("getEnrollmentSpecificId"); if (mService == null) { @@ -17330,4 +17363,46 @@ public class DevicePolicyManager { } return new HashSet<>(); } + + /** + * Controls the maximum storage size allowed for policies associated with an admin. + * Setting a limit of -1 effectively removes any storage restrictions. + * + * @param storageLimit Maximum storage allowed in bytes. Use -1 to disable limits. + * + * @hide + */ + @SystemApi + @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED) + public void setMaxPolicyStorageLimit(int storageLimit) { + if (mService != null) { + try { + mService.setMaxPolicyStorageLimit(mContext.getPackageName(), storageLimit); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Retrieves the current maximum storage limit for policies associated with an admin. + * + * @return The maximum storage limit in bytes, or -1 if no limit is enforced. + * + * @hide + */ + @SystemApi + @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + @FlaggedApi(FLAG_DEVICE_POLICY_SIZE_TRACKING_ENABLED) + public int getMaxPolicyStorageLimit() { + if (mService != null) { + try { + return mService.getMaxPolicyStorageLimit(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return -1; + } }
\ No newline at end of file diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 304359bc6105..07ee8de587f8 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -333,4 +333,9 @@ public abstract class DevicePolicyManagerInternal { */ public abstract List<EnforcingUser> getUserRestrictionSources(String restriction, @UserIdInt int userId); + + /** + * Enforces resolved security logging policy, should only be invoked from device policy engine. + */ + public abstract void enforceSecurityLoggingPolicy(boolean enabled); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index f72fdc069db5..f2466acc59bf 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -392,7 +392,7 @@ interface IDevicePolicyManager { boolean getDoNotAskCredentialsOnBoot(); void notifyPendingSystemUpdate(in SystemUpdateInfo info); - SystemUpdateInfo getPendingSystemUpdate(in ComponentName admin); + SystemUpdateInfo getPendingSystemUpdate(in ComponentName admin, in String callerPackage); void setPermissionPolicy(in ComponentName admin, in String callerPackage, int policy); int getPermissionPolicy(in ComponentName admin); @@ -615,4 +615,7 @@ interface IDevicePolicyManager { int getContentProtectionPolicy(in ComponentName who, String callerPackageName); int[] getSubscriptionIds(String callerPackageName); + + void setMaxPolicyStorageLimit(String packageName, int storageLimit); + int getMaxPolicyStorageLimit(String packageName); } diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 30cd1b72fd49..726ddad37cfb 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -92,6 +92,13 @@ flag { } flag { + name: "allow_querying_profile_type" + namespace: "enterprise" + description: "Public APIs to query if a user is a profile and what kind of profile type it is." + bug: "323001115" +} + +flag { name: "quiet_mode_credential_bug_fix" namespace: "enterprise" description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped." diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 7a4a3f9c8f27..9fa73627de05 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -1,5 +1,7 @@ package android.app.assist; +import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION; + import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1278,7 +1280,7 @@ public class AssistStructure implements Parcelable { * * @hide */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable public GetCredentialRequest getCredentialManagerRequest() { return mGetCredentialRequest; @@ -1291,7 +1293,7 @@ public class AssistStructure implements Parcelable { * @hide * */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable public OutcomeReceiver<GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() { diff --git a/core/java/android/app/wearable/IWearableSensingManager.aidl b/core/java/android/app/wearable/IWearableSensingManager.aidl index 3cbc8a21d2d8..f67802279e26 100644 --- a/core/java/android/app/wearable/IWearableSensingManager.aidl +++ b/core/java/android/app/wearable/IWearableSensingManager.aidl @@ -17,6 +17,7 @@ package android.app.wearable; import android.app.PendingIntent; +import android.content.ComponentName; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; @@ -38,4 +39,8 @@ interface IWearableSensingManager { void registerDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") void unregisterDataRequestObserver(int dataType, in PendingIntent dataRequestPendingIntent, in RemoteCallback statusCallback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + void startHotwordRecognition(in ComponentName targetVisComponentName, in RemoteCallback statusCallback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)") + void stopHotwordRecognition(in RemoteCallback statusCallback); }
\ No newline at end of file diff --git a/core/java/android/app/wearable/WearableSensingManager.java b/core/java/android/app/wearable/WearableSensingManager.java index 3b281e9c1ae2..637f6776bd1b 100644 --- a/core/java/android/app/wearable/WearableSensingManager.java +++ b/core/java/android/app/wearable/WearableSensingManager.java @@ -28,6 +28,7 @@ import android.annotation.SystemService; import android.app.PendingIntent; import android.app.ambientcontext.AmbientContextEvent; import android.companion.CompanionDeviceManager; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Binder; @@ -92,9 +93,13 @@ public class WearableSensingManager { public static final int STATUS_SUCCESS = 1; /** - * The value of the status code that indicates one or more of the - * requested events are not supported. + * The value of the status code that indicates one or more of the requested events are not + * supported. */ + // TODO(b/324635656): Deprecate this status code. Update Javadoc: + // @deprecated WearableSensingManager does not deal with events. Use {@link + // STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of + // {@link WearableSensingService}. public static final int STATUS_UNSUPPORTED = 2; /** @@ -382,6 +387,83 @@ public class WearableSensingManager { } } + /** + * Requests the wearable to start hotword recognition. + * + * <p>When this method is called, the system will attempt to provide a {@link + * android.service.wearable.WearableHotwordAudioConsumer} to {@link WearableSensingService}. + * After first-stage hotword is detected on a wearable, {@link WearableSensingService} should + * send the hotword audio to the {@link android.service.wearable.WearableHotwordAudioConsumer}, + * which will forward the data to the {@link android.service.voice.HotwordDetectionService} for + * second-stage hotword validation. If hotword is detected there, the audio data will be + * forwarded to the {@link android.service.voice.VoiceInteractionService}. + * + * <p>If the {@code targetVisComponentName} provided here is not null, when {@link + * WearableSensingService} sends hotword audio to the {@link + * android.service.wearable.WearableHotwordAudioConsumer}, the system will check whether the + * {@link android.service.voice.VoiceInteractionService} at that time is {@code + * targetVisComponentName}. If not, the system will call {@link + * WearableSensingService#onActiveHotwordAudioStopRequested()} and will not forward the audio + * data to the current {@link android.service.voice.HotwordDetectionService} nor {@link + * android.service.voice.VoiceInteractionService}. The system will not send a status code to + * {@code statusConsumer} regarding the {@code targetVisComponentName} check. The caller is + * responsible for determining whether the system's {@link + * android.service.voice.VoiceInteractionService} is the same as {@code targetVisComponentName}. + * The check here is just a protection against race conditions. + * + * <p>Calling this method again will send a new {@link + * android.service.wearable.WearableHotwordAudioConsumer} to {@link WearableSensingService}. For + * audio data sent to the new consumer, the system will perform the above check using the newly + * provided {@code targetVisComponentName}. The {@link WearableSensingService} should not + * continue to use the previous consumers after receiving a new one. + * + * <p>If the {@code statusConsumer} returns {@link STATUS_SUCCESS}, the caller should call + * {@link #stopListeningForHotword(Executor, Consumer)} when it wants the wearable to stop + * listening for hotword. If the {@code statusConsumer} returns any other status code, a failure + * has occurred and calling {@link #stopListeningForHotword(Executor, Consumer)} is not + * required. The system will not retry listening automatically. The caller should call this + * method again if they want to retry. + * + * <p>If a failure occurred after the {@link statusConsumer} returns {@link STATUS_SUCCESS}, + * {@link statusConsumer} will be invoked again with a status code other than {@link + * STATUS_SUCCESS}. + * + * @param targetVisComponentName The ComponentName of the target VoiceInteractionService. + * @param executor Executor on which to run the consumer callback. + * @param statusConsumer A consumer that handles the status codes. + */ + @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + public void startHotwordRecognition( + @Nullable ComponentName targetVisComponentName, + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + try { + mService.startHotwordRecognition( + targetVisComponentName, createStatusCallback(executor, statusConsumer)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Requests the wearable to stop hotword recognition. + * + * @param executor Executor on which to run the consumer callback. + * @param statusConsumer A consumer that handles the status codes. + */ + @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + @RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE) + public void stopHotwordRecognition( + @NonNull @CallbackExecutor Executor executor, + @NonNull @StatusCode Consumer<Integer> statusConsumer) { + try { + mService.stopHotwordRecognition(createStatusCallback(executor, statusConsumer)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private static RemoteCallback createStatusCallback( Executor executor, Consumer<Integer> statusConsumer) { return new RemoteCallback( diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e8031a374310..0bcbb8e1868c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -19,6 +19,7 @@ package android.content; import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY; 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.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA; import android.Manifest; @@ -3902,6 +3903,26 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.ACTION_IDLE_MAINTENANCE_END"; /** + * Broadcast Action: A broadcast sent to the main user when the main user changes their + * Lock Screen Knowledge Factor, either because they changed the current value, or because + * they added or removed it. + * + * <p class="note">At present, this intent is only broadcast to listeners with the + * CONFIGURE_FACTORY_RESET_PROTECTION signature|privileged permiession.</p> + * + * <p class="note">This is a protected intent that can only be sent by the system.</p> + * + * @hide + */ + @FlaggedApi(FLAG_FRP_ENFORCEMENT) + @SystemApi + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(protectedBroadcast = true) + public static final String + ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED = + "android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED"; + + /** * Broadcast Action: a remote intent is to be broadcasted. * * A remote intent is used for remote RPC between devices. The remote intent diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 529363f828bb..8220313a9197 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -19,7 +19,9 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; +import static android.app.admin.flags.Flags.FLAG_ALLOW_QUERYING_PROFILE_TYPE; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -314,6 +316,41 @@ public class CrossProfileApps { } } + + /** + * Checks if the specified user is a profile, i.e. not the parent user. + * + * @param userHandle The UserHandle of the target profile, must be one of the users returned by + * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will + * be thrown. + * @return whether the specified user is a profile. + */ + @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE) + public boolean isProfile(@NonNull UserHandle userHandle) { + // Note that this is not a security check, but rather a check for correct use. + // The actual security check is performed by UserManager. + verifyCanAccessUser(userHandle); + + return mUserManager.isProfile(userHandle.getIdentifier()); + } + + /** + * Checks if the specified user is a managed profile. + * + * @param userHandle The UserHandle of the target profile, must be one of the users returned by + * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will + * be thrown. + * @return whether the specified user is a managed profile. + */ + @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE) + public boolean isManagedProfile(@NonNull UserHandle userHandle) { + // Note that this is not a security check, but rather a check for correct use. + // The actual security check is performed by UserManager. + verifyCanAccessUser(userHandle); + + return mUserManager.isManagedProfile(userHandle.getIdentifier()); + } + /** * Return a label that calling app can show to user for the semantic of profile switching -- * launching its own activity in specified user profile. For example, it may return @@ -677,6 +714,11 @@ public class CrossProfileApps { } } + /** + * A validation method to check that the methods in this class are only being applied to user + * handles returned by {@link #getTargetUserProfiles()}. As this is run client-side for + * input validation purposes, this should never replace a real security check service-side. + */ private void verifyCanAccessUser(UserHandle userHandle) { if (!getTargetUserProfiles().contains(userHandle)) { throw new SecurityException("Not allowed to access " + userHandle); diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 7c264f65d471..9c859c442355 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -17,6 +17,8 @@ package android.content.pm; import static android.Manifest.permission; +import static android.Manifest.permission.ACCESS_HIDDEN_PROFILES; +import static android.Manifest.permission.ACCESS_HIDDEN_PROFILES_FULL; import static android.Manifest.permission.READ_FRAME_BUFFER; import android.annotation.CallbackExecutor; @@ -779,15 +781,20 @@ public class LauncherApps { /** * Returns information related to a user which is useful for displaying UI elements - * to distinguish it from other users (eg, badges). Only system launchers should - * call this API. + * to distinguish it from other users (eg, badges). * - * @param userHandle user handle of the user for which LauncherUserInfo is requested - * @return the LauncherUserInfo object related to the user specified. - * @hide + * <p>If the user in question is a hidden profile like + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. + * + * @param userHandle user handle of the user for which LauncherUserInfo is requested. + * @return the {@link LauncherUserInfo} object related to the user specified, null in case + * the user is inaccessible. */ @Nullable @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public final LauncherUserInfo getLauncherUserInfo(@NonNull UserHandle userHandle) { if (DEBUG) { Log.i(TAG, "getLauncherUserInfo " + userHandle); @@ -823,17 +830,20 @@ public class LauncherApps { * </ul> * </p> * - * + * <p>If the user in question is a hidden profile + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. * * @param packageName the package for which intent sender to launch App Market Activity is * required. * @param user the profile for which intent sender to launch App Market Activity is required. * @return {@link IntentSender} object which launches the App Market Activity, null in case * there is no such activity. - * @hide */ @Nullable @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public IntentSender getAppMarketActivityIntent(@Nullable String packageName, @NonNull UserHandle user) { if (DEBUG) { @@ -851,15 +861,21 @@ public class LauncherApps { /** * Returns the list of the system packages that are installed at user creation. * - * <p>An empty list denotes that all system packages are installed for that user at creation. - * This behaviour is inherited from the underlining UserManager API. + * <p>An empty list denotes that all system packages should be treated as pre-installed for that + * user at creation. + * + * <p>If the user in question is a hidden profile like + * {@link UserManager.USER_TYPE_PROFILE_PRIVATE}, caller should have + * {@link android.app.role.RoleManager.ROLE_HOME} and either of the permissions required. * * @param userHandle the user for which installed system packages are required. * @return {@link List} of {@link String}, representing the package name of the installed * package. Can be empty but not null. - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) + @NonNull + @RequiresPermission(conditional = true, + anyOf = {ACCESS_HIDDEN_PROFILES_FULL, ACCESS_HIDDEN_PROFILES}) public List<String> getPreInstalledSystemPackages(@NonNull UserHandle userHandle) { if (DEBUG) { Log.i(TAG, "getPreInstalledSystemPackages for user: " + userHandle); diff --git a/core/java/android/content/pm/LauncherUserInfo.java b/core/java/android/content/pm/LauncherUserInfo.java index 214c3e48db71..8426f54d4754 100644 --- a/core/java/android/content/pm/LauncherUserInfo.java +++ b/core/java/android/content/pm/LauncherUserInfo.java @@ -27,8 +27,6 @@ import android.os.UserManager; /** * The LauncherUserInfo object holds information about an Android user that is required to display * the Launcher related UI elements specific to the user (like badges). - * - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) public final class LauncherUserInfo implements Parcelable { @@ -41,11 +39,9 @@ public final class LauncherUserInfo implements Parcelable { /** * Returns type of the user as defined in {@link UserManager}. e.g., * {@link UserManager.USER_TYPE_PROFILE_MANAGED} or {@link UserManager.USER_TYPE_PROFILE_ClONE} - * TODO(b/303812736): Make the return type public and update javadoc here once the linked bug - * is resolved. + * or {@link UserManager.USER_TYPE_PROFILE_PRIVATE} * * @return the userType for the user whose LauncherUserInfo this is - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) @NonNull @@ -58,7 +54,6 @@ public final class LauncherUserInfo implements Parcelable { * {@link UserManager#getSerialNumberForUser(UserHandle)} * * @return the serial number associated with the user - * @hide */ @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE) public int getUserSerialNumber() { diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index c7d93bfbb83d..6696ba035f30 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -139,6 +139,20 @@ flag { flag { name: "enable_launcher_apps_hidden_profile_checks" namespace: "profile_experiences" - description: "Enable extra check to limit access to hidden prfiles data in Launcher apps APIs." + description: "Enable extra check to limit access to hidden profiles data in Launcher apps APIs." bug: "321988638" } + +flag { + name: "reorder_wallpaper_during_user_switch" + namespace: "multiuser" + description: "Reorder loading home and lock screen wallpapers during a user switch." + bug: "324911115" +} + +flag { + name: "set_power_mode_during_user_switch" + namespace: "multiuser" + description: "Set power mode during a user switch." + bug: "325249845" +} diff --git a/core/java/android/credentials/selection/FailureResult.java b/core/java/android/credentials/selection/FailureResult.java index 93ba671fa14f..91f7013cd023 100644 --- a/core/java/android/credentials/selection/FailureResult.java +++ b/core/java/android/credentials/selection/FailureResult.java @@ -23,6 +23,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Intent; +import android.os.Bundle; +import android.os.ResultReceiver; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +38,24 @@ import java.lang.annotation.RetentionPolicy; @SystemApi @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) public final class FailureResult { + + /** + * Sends the {@code failureResult} that caused the UI to stop back to the CredentialManager + * service. + * + * @param resultReceiver the ResultReceiver sent from the system service, that can be extracted + * from the launch intent via + * {@link IntentHelper#extractResultReceiver(Intent)} + */ + public static void sendFailureResult(@NonNull ResultReceiver resultReceiver, + @NonNull FailureResult failureResult) { + FailureDialogResult result = failureResult.toFailureDialogResult(); + Bundle resultData = new Bundle(); + FailureDialogResult.addToBundle(result, resultData); + resultReceiver.send(failureResult.errorCodeToResultCode(), + resultData); + } + @Nullable private final String mErrorMessage; @NonNull @@ -53,6 +74,9 @@ public final class FailureResult { /** * The UI was stopped due to a failure, e.g. because it failed to parse the incoming data, * or it encountered an irrecoverable internal issue. + * + * This code also serves as a default value to use for failures that do not fall into any other + * error code category or for backward compatibility. */ public static final int ERROR_CODE_UI_FAILURE = 0; /** The user intentionally canceled the dialog. */ diff --git a/core/java/android/credentials/selection/ResultHelper.java b/core/java/android/credentials/selection/ResultHelper.java deleted file mode 100644 index d6347b0086ff..000000000000 --- a/core/java/android/credentials/selection/ResultHelper.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.credentials.selection; - -import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED; - -import android.annotation.FlaggedApi; -import android.annotation.NonNull; -import android.annotation.SystemApi; -import android.content.Intent; -import android.os.Bundle; -import android.os.ResultReceiver; - -/** - * Utilities for sending the UI results back to the system service. - * - * @hide - */ -@SystemApi -@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) -public final class ResultHelper { - /** - * Sends the {@code failureResult} that caused the UI to stop back to the CredentialManager - * service. - * - * @param resultReceiver the ResultReceiver sent from the system service, that can be extracted - * from the launch intent via - * {@link IntentHelper#extractResultReceiver(Intent)} - */ - public static void sendFailureResult(@NonNull ResultReceiver resultReceiver, - @NonNull FailureResult failureResult) { - FailureDialogResult result = failureResult.toFailureDialogResult(); - Bundle resultData = new Bundle(); - FailureDialogResult.addToBundle(result, resultData); - resultReceiver.send(failureResult.errorCodeToResultCode(), - resultData); - } - - /** - * Sends the completed {@code userSelectionResult} back to the CredentialManager service. - * - * @param resultReceiver the ResultReceiver sent from the system service, that can be extracted - * from the launch intent via - * {@link IntentHelper#extractResultReceiver(Intent)} - */ - public static void sendUserSelectionResult(@NonNull ResultReceiver resultReceiver, - @NonNull UserSelectionResult userSelectionResult) { - UserSelectionDialogResult result = userSelectionResult.toUserSelectionDialogResult(); - Bundle resultData = new Bundle(); - UserSelectionDialogResult.addToBundle(result, resultData); - resultReceiver.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION, - resultData); - } - - private ResultHelper() {} -} diff --git a/core/java/android/credentials/selection/UserSelectionResult.java b/core/java/android/credentials/selection/UserSelectionResult.java index 235a5d5723ea..140640e2151a 100644 --- a/core/java/android/credentials/selection/UserSelectionResult.java +++ b/core/java/android/credentials/selection/UserSelectionResult.java @@ -22,6 +22,9 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Intent; +import android.os.Bundle; +import android.os.ResultReceiver; import com.android.internal.util.Preconditions; @@ -34,6 +37,22 @@ import com.android.internal.util.Preconditions; @SystemApi @FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED) public final class UserSelectionResult { + /** + * Sends the completed {@code userSelectionResult} back to the CredentialManager service. + * + * @param resultReceiver the ResultReceiver sent from the system service, that can be extracted + * from the launch intent via + * {@link IntentHelper#extractResultReceiver(Intent)} + */ + public static void sendUserSelectionResult(@NonNull ResultReceiver resultReceiver, + @NonNull UserSelectionResult userSelectionResult) { + UserSelectionDialogResult result = userSelectionResult.toUserSelectionDialogResult(); + Bundle resultData = new Bundle(); + UserSelectionDialogResult.addToBundle(result, resultData); + resultReceiver.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION, + resultData); + } + @NonNull private final String mProviderId; @NonNull diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index 870546a6fd2b..ba356bb55194 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -40,7 +40,7 @@ import dalvik.system.CloseGuard; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.CursorWindow_host") + "com.android.platform.test.ravenwood.nativesubstitution.CursorWindow_host") public class CursorWindow extends SQLiteClosable implements Parcelable { private static final String STATS_TAG = "CursorWindowStats"; diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl new file mode 100644 index 000000000000..838e41ee1c08 --- /dev/null +++ b/core/java/android/hardware/CameraPrivacyAllowlistEntry.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.hardware; + +/** @hide */ +parcelable CameraPrivacyAllowlistEntry { + String packageName; + boolean isMandatory; +} + diff --git a/core/java/android/hardware/ISensorPrivacyListener.aidl b/core/java/android/hardware/ISensorPrivacyListener.aidl index 2ac21d2615aa..19ae302355be 100644 --- a/core/java/android/hardware/ISensorPrivacyListener.aidl +++ b/core/java/android/hardware/ISensorPrivacyListener.aidl @@ -25,5 +25,6 @@ oneway interface ISensorPrivacyListener { // frameworks/native/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyListener.aidl // =============== Beginning of transactions used on native side as well ====================== void onSensorPrivacyChanged(int toggleType, int sensor, boolean enabled); + void onSensorPrivacyStateChanged(int toggleType, int sensor, int state); // =============== End of transactions used on native side as well ============================ } diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index 9cf329ca3d3d..851ce2add94f 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -16,6 +16,7 @@ package android.hardware; +import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; /** @hide */ @@ -45,6 +46,22 @@ interface ISensorPrivacyManager { void setToggleSensorPrivacy(int userId, int source, int sensor, boolean enable); void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") + List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist(); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") + int getToggleSensorPrivacyState(int toggleType, int sensor); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)") + void setToggleSensorPrivacyState(int userId, int source, int sensor, int state); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)") + void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, int state); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)") + boolean isCameraPrivacyEnabled(String packageName); + // =============== End of transactions used on native side as well ============================ void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token, @@ -53,4 +70,4 @@ interface ISensorPrivacyManager { boolean requiresAuthentication(); void showSensorUseDialog(int sensor); -}
\ No newline at end of file +} diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index 18c95bfbb297..6294a8d617de 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -17,6 +17,7 @@ package android.hardware; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; @@ -38,9 +39,11 @@ import android.util.Log; import android.util.Pair; import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; @@ -215,13 +218,41 @@ public final class SensorPrivacyManager { public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED; /** + * Constant indicating privacy is enabled except for the automotive driver assistance apps + * which are helpful for driving. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = + SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS; + + /** + * Constant indicating privacy is enabled except for the automotive driver assistance apps + * which are required by car manufacturer for driving. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = + SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS; + + /** + * Constant indicating privacy is enabled except for the automotive driver assistance apps + * which are both helpful for driving and also apps required by car manufacturer for + * driving. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = + SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS; + + /** * Types of state which can exist for a sensor privacy toggle * * @hide */ @IntDef(value = { ENABLED, - DISABLED + DISABLED, + AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS, + AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS, + AUTOMOTIVE_DRIVER_ASSISTANCE_APPS }) @Retention(RetentionPolicy.SOURCE) public @interface StateType {} @@ -266,6 +297,19 @@ public final class SensorPrivacyManager { private int mToggleType; private int mSensor; private boolean mEnabled; + private int mState; + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private SensorPrivacyChangedParams(int toggleType, int sensor, int state) { + mToggleType = toggleType; + mSensor = sensor; + mState = state; + if (state == StateTypes.ENABLED) { + mEnabled = true; + } else { + mEnabled = false; + } + } private SensorPrivacyChangedParams(int toggleType, int sensor, boolean enabled) { mToggleType = toggleType; @@ -284,6 +328,12 @@ public final class SensorPrivacyManager { public boolean isEnabled() { return mEnabled; } + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public @StateTypes.StateType int getState() { + return mState; + } + } } @@ -319,6 +369,9 @@ public final class SensorPrivacyManager { private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>, OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>(); + @GuardedBy("mLock") + private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null; + /** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local * listeners */ @NonNull @@ -328,12 +381,33 @@ public final class SensorPrivacyManager { synchronized (mLock) { for (int i = 0; i < mToggleListeners.size(); i++) { OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i); + if (Flags.cameraPrivacyAllowlist()) { + int state = enabled ? StateTypes.ENABLED : StateTypes.DISABLED; + mToggleListeners.valueAt(i).execute(() -> listener + .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener + .SensorPrivacyChangedParams(toggleType, sensor, state))); + } else { + mToggleListeners.valueAt(i).execute(() -> listener + .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener + .SensorPrivacyChangedParams(toggleType, sensor, enabled))); + } + } + } + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) { + synchronized (mLock) { + for (int i = 0; i < mToggleListeners.size(); i++) { + OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i); mToggleListeners.valueAt(i).execute(() -> listener .onSensorPrivacyChanged(new OnSensorPrivacyChangedListener - .SensorPrivacyChangedParams(toggleType, sensor, enabled))); + .SensorPrivacyChangedParams(toggleType, sensor, state))); } } } + }; /** Whether the singleton ISensorPrivacyListener has been registered */ @@ -649,6 +723,73 @@ public final class SensorPrivacyManager { } /** + * Returns sensor privacy state for a specific sensor. + * + * @return int sensor privacy state. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public @StateTypes.StateType int getSensorPrivacyState(@ToggleType int toggleType, + @Sensors.Sensor int sensor) { + try { + return mService.getToggleSensorPrivacyState(toggleType, sensor); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns if camera privacy is enabled for a specific package. + * + * @return boolean sensor privacy state. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public boolean isCameraPrivacyEnabled(@NonNull String packageName) { + try { + return mService.isCameraPrivacyEnabled(packageName); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns camera privacy allowlist. + * + * @return List of automotive driver assistance packages for + * privacy allowlisting. The returned map includes the package + * name as key and the value is a Boolean which tells if that package + * is required by the car manufacturer as mandatory package for driving. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public @NonNull Map<String, Boolean> getCameraPrivacyAllowlist() { + synchronized (mLock) { + if (mCameraPrivacyAllowlist == null) { + mCameraPrivacyAllowlist = new ArrayMap<>(); + try { + for (CameraPrivacyAllowlistEntry entry : + mService.getCameraPrivacyAllowlist()) { + mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mCameraPrivacyAllowlist; + } + } + + /** * Sets sensor privacy to the specified state for an individual sensor. * * @param sensor the sensor which to change the state for @@ -677,6 +818,22 @@ public final class SensorPrivacyManager { * Sets sensor privacy to the specified state for an individual sensor. * * @param sensor the sensor which to change the state for + * @param state the state to which sensor privacy should be set. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void setSensorPrivacyState(@Sensors.Sensor int sensor, + @StateTypes.StateType int state) { + setSensorPrivacyState(resolveSourceFromCurrentContext(), sensor, state); + } + + /** + * Sets sensor privacy to the specified state for an individual sensor. + * + * @param sensor the sensor which to change the state for * @param enable the state to which sensor privacy should be set. * * @hide @@ -708,6 +865,27 @@ public final class SensorPrivacyManager { } /** + * Sets sensor privacy to the specified state for an individual sensor. + * + * @param sensor the sensor which to change the state for + * @param state the state to which sensor privacy should be set. + * + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void setSensorPrivacyState(@Sources.Source int source, @Sensors.Sensor int sensor, + @StateTypes.StateType int state) { + try { + mService.setToggleSensorPrivacyState(mContext.getUserId(), source, sensor, state); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + } + + /** * Sets sensor privacy to the specified state for an individual sensor for the profile group of * context's user. * @@ -745,6 +923,28 @@ public final class SensorPrivacyManager { } /** + * Sets sensor privacy to the specified state for an individual sensor for the profile group of + * context's user. + * + * @param source the source using which the sensor is toggled. + * @param sensor the sensor which to change the state for + * @param state the state to which sensor privacy should be set. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void setSensorPrivacyStateForProfileGroup(@Sources.Source int source, + @Sensors.Sensor int sensor, @StateTypes.StateType int state) { + try { + mService.setToggleSensorPrivacyStateForProfileGroup(mContext.getUserId(), source, + sensor, state); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Don't show dialogs to turn off sensor privacy for this package. * * @param suppress Whether to suppress or re-enable. @@ -865,6 +1065,12 @@ public final class SensorPrivacyManager { boolean enabled) { listener.onAllSensorPrivacyChanged(enabled); } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void onSensorPrivacyStateChanged(int toggleType, int sensor, + int state) { + } }; mListeners.put(listener, iListener); } diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 451d6fba0105..2add77e44dd3 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.DeviceStateSensorOrientationMap; @@ -6076,6 +6077,28 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> JPEGR_AVAILABLE_JPEG_R_STALL_DURATIONS_MAXIMUM_RESOLUTION = new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.jpegr.availableJpegRStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class); + /** + * <p>Minimum and maximum padding zoom factors supported by this camera device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension.</p> + * <p>The minimum and maximum padding zoom factors supported by the device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension feature. This extension specific camera characteristic can be queried using + * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p> + * <p><b>Units</b>: A pair of padding zoom factors in floating-points: + * (minPaddingZoomFactor, maxPaddingZoomFactor)</p> + * <p><b>Range of valid values:</b><br></p> + * <p>1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE = + new Key<android.util.Range<Float>>("android.efv.paddingZoomFactorRange", new TypeReference<android.util.Range<Float>>() {{ }}); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index 3b10e0dd516a..f6b22edaafb1 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.ImageFormat; +import android.hardware.camera2.CameraCharacteristics.Key; import android.hardware.camera2.extension.IAdvancedExtenderImpl; import android.hardware.camera2.extension.ICameraExtensionsProxyService; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; @@ -35,6 +36,8 @@ import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.SizeList; import android.hardware.camera2.impl.CameraExtensionUtils; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; +import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Binder; @@ -1497,4 +1500,28 @@ public final class CameraExtensionCharacteristics { return Collections.unmodifiableSet(ret); } + + + /** + * <p>Minimum and maximum padding zoom factors supported by this camera device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for + * the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension.</p> + * <p>The minimum and maximum padding zoom factors supported by the device for + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension feature. This extension specific camera characteristic can be queried using + * {@link android.hardware.camera2.CameraExtensionCharacteristics#get}.</p> + * <p><b>Units</b>: A pair of padding zoom factors in floating-points: + * (minPaddingZoomFactor, maxPaddingZoomFactor)</p> + * <p><b>Range of valid values:</b><br></p> + * <p>1.0 < minPaddingZoomFactor <= maxPaddingZoomFactor</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Range<Float>> EFV_PADDING_ZOOM_FACTOR_RANGE = + CameraCharacteristics.EFV_PADDING_ZOOM_FACTOR_RANGE; } diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 7cf10d89004f..e24c98e98c5d 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.util.Log; @@ -276,8 +277,11 @@ public abstract class CameraMetadata<TKey> { throw new IllegalArgumentException("key type must be that of a metadata key"); } - if (field.getAnnotation(PublicKey.class) == null) { - // Never expose @hide keys up to the API user + if (field.getAnnotation(PublicKey.class) == null + && field.getAnnotation(ExtensionKey.class) == null) { + // Never expose @hide keys to the API user unless they are + // marked as @ExtensionKey, as these keys are publicly accessible via + // the extension key classes. return false; } @@ -3893,6 +3897,36 @@ public abstract class CameraMetadata<TKey> { public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2; // + // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE + // + + /** + * <p>No stabilization.</p> + * @see CaptureRequest#EFV_STABILIZATION_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_OFF = 0; + + /** + * <p>Gimbal stabilization mode.</p> + * @see CaptureRequest#EFV_STABILIZATION_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_GIMBAL = 1; + + /** + * <p>Locked stabilization mode which uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization to directionally steady the target region.</p> + * @see CaptureRequest#EFV_STABILIZATION_MODE + * @hide + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_LOCKED = 2; + + // // Enumeration values for CaptureResult#CONTROL_AE_STATE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index ded96a23e11e..66efccd14097 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.params.OutputConfiguration; @@ -4292,6 +4293,146 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> public static final Key<Integer> EXTENSION_STRENGTH = new Key<Integer>("android.extension.strength", int.class); + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } + * mode. If android.efv.paddingZoomFactor is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * android.efv.paddingZoomFactor to at least 1.5.</p> + * <p>If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden. + * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the + * padding zoom factor during android.efv.autoZoom.</p> + * <p><b>Range of valid values:</b><br> + * android.efv.paddingZoomFactorRange</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.paddingZoomFactor", float.class); + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting + * to control image quality further using android.efv.maxPaddingZoomFactor.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = + new Key<Boolean>("android.efv.autoZoom", boolean.class); + + /** + * <p>Used to limit the android.efv.paddingZoomFactor if + * android.efv.autoZoom is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If android.efv.autoZoom is enabled, this key can be used to set a limit + * on the android.efv.paddingZoomFactor chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to + * the android.efv.paddingZoomFactor to effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.maxPaddingZoomFactor", float.class); + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = + new Key<Integer>("android.efv.stabilizationMode", int.class); + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)]. + * The range for the vertical shift is + * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = + new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }}); + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = + new Key<Float>("android.efv.rotateViewport", float.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 7cf5a7f2e445..a01c23d984f4 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.impl.CaptureResultExtras; +import android.hardware.camera2.impl.ExtensionKey; import android.hardware.camera2.impl.PublicKey; import android.hardware.camera2.impl.SyntheticKey; import android.hardware.camera2.utils.TypeReference; @@ -5919,6 +5920,214 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { public static final Key<Integer> EXTENSION_STRENGTH = new Key<Integer>("android.extension.strength", int.class); + /** + * <p>The padding region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>The padding region denotes the area surrounding the stabilized target region within which + * the camera can be moved while maintaining the target region in view. As the camera moves, + * the padding region adjusts to represent the proximity of the target region to the + * boundary, which is the point at which the target region will start to go out of bounds.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction. + * The pixels reference the active array coordinate system. Negative values indicate the target + * region is out of bounds. The value for this key may be null for when the stabilization mode is + * in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_OFF } + * or {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_GIMBAL } mode.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_PADDING_REGION = + new Key<int[]>("android.efv.paddingRegion", int[].class); + + /** + * <p>The padding region when android.efv.autoZoom is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>This may differ from android.efv.paddingRegion as the field of view can change + * during android.efv.autoZoom, altering the boundary region and thus updating the padding between the + * target region and the boundary.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction + * when android.efv.autoZoom is enabled. Negative values indicate the target region is out of bounds. + * The value for this key may be null for when the android.efv.autoZoom is not enabled.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = + new Key<int[]>("android.efv.autoZoomPaddingRegion", int[].class); + + /** + * <p>List of coordinates representing the target region relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE } + * for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in + * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A list of android.graphics.PointF that define the coordinates of the target region + * relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }. + * The array represents the target region coordinates as: top-left, top-right, bottom-left, + * bottom-right.</p> + * <p><b>Range of valid values:</b><br> + * The list of target coordinates will define a region within the bounds of the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = + new Key<android.graphics.PointF[]>("android.efv.targetCoordinates", android.graphics.PointF[].class); + + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } + * mode. If android.efv.paddingZoomFactor is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * android.efv.paddingZoomFactor to at least 1.5.</p> + * <p>If android.efv.autoZoom is enabled, the requested android.efv.paddingZoomFactor will be overridden. + * android.efv.maxPaddingZoomFactor can be checked for more details on controlling the + * padding zoom factor during android.efv.autoZoom.</p> + * <p><b>Range of valid values:</b><br> + * android.efv.paddingZoomFactorRange</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.paddingZoomFactor", float.class); + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = + new Key<Integer>("android.efv.stabilizationMode", int.class); + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and android.efv.paddingZoomFactor + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * android.efv.paddingZoomFactor. A limit can be set on the padding zoom if wanting + * to control image quality further using android.efv.maxPaddingZoomFactor.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = + new Key<Boolean>("android.efv.autoZoom", boolean.class); + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = + new Key<Float>("android.efv.rotateViewport", float.class); + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max(android.efv.paddingRegion-left), max(android.efv.paddingRegion-right)]. + * The range for the vertical shift is + * [-max(android.efv.paddingRegion-top), max(android.efv.paddingRegion-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = + new Key<android.util.Pair<Integer,Integer>>("android.efv.translateViewport", new TypeReference<android.util.Pair<Integer,Integer>>() {{ }}); + + /** + * <p>Used to limit the android.efv.paddingZoomFactor if + * android.efv.autoZoom is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If android.efv.autoZoom is enabled, this key can be used to set a limit + * on the android.efv.paddingZoomFactor chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.CameraMetadata#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of android.efv.paddingZoomFactorRange. Use a value greater than or equal to + * the android.efv.paddingZoomFactor to effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @hide + */ + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = + new Key<Float>("android.efv.maxPaddingZoomFactor", float.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/ExtensionCaptureRequest.java b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java new file mode 100644 index 000000000000..32039c6ec0ba --- /dev/null +++ b/core/java/android/hardware/camera2/ExtensionCaptureRequest.java @@ -0,0 +1,227 @@ +/* + * 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.hardware.camera2; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CaptureRequest; +import android.hardware.camera2.CaptureRequest.Key; +import android.hardware.camera2.impl.ExtensionKey; +import android.hardware.camera2.impl.PublicKey; + +import com.android.internal.camera.flags.Flags; + +/** + * ExtensionCaptureRequest contains definitions for extension-specific CaptureRequest keys that + * can be used to configure a {@link android.hardware.camera2.CaptureRequest} during a + * {@link android.hardware.camera2.CameraExtensionSession}. + * + * Note that ExtensionCaptureRequest is not intended to be used as a replacement + * for CaptureRequest in the extensions. It serves as a supplementary class providing + * extension-specific CaptureRequest keys. Developers should use these keys in conjunction + * with regular CaptureRequest objects during a + * {@link android.hardware.camera2.CameraExtensionSession}. + * + * @see CaptureRequest + * @see CameraExtensionSession + */ +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public final class ExtensionCaptureRequest { + + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } + * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden. + * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the + * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.</p> + * <p><b>Range of valid values:</b><br> + * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_PADDING_ZOOM_FACTOR; + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting + * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureRequest.EFV_AUTO_ZOOM; + + /** + * <p>Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if + * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit + * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE Range}. Use a value greater than or equal to + * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to + * effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureRequest.EFV_MAX_PADDING_ZOOM_FACTOR; + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureRequest.EFV_STABILIZATION_MODE; + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)]. + * The range for the vertical shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureResult#EFV_PADDING_REGION + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureRequest.EFV_TRANSLATE_VIEWPORT; + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureRequest.EFV_ROTATE_VIEWPORT; + + + // + // Enumeration values for CaptureRequest#EFV_STABILIZATION_MODE + // + + /** + * <p>No stabilization.</p> + * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_OFF = CaptureRequest.EFV_STABILIZATION_MODE_OFF; + + /** + * <p>Gimbal stabilization mode.</p> + * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_GIMBAL = CaptureRequest.EFV_STABILIZATION_MODE_GIMBAL; + + /** + * <p>Locked stabilization mode which uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization to directionally steady the target region.</p> + * @see ExtensionCaptureRequest#EFV_STABILIZATION_MODE + */ + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final int EFV_STABILIZATION_MODE_LOCKED = CaptureRequest.EFV_STABILIZATION_MODE_LOCKED; + +}
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/ExtensionCaptureResult.java b/core/java/android/hardware/camera2/ExtensionCaptureResult.java new file mode 100644 index 000000000000..5c9990975a9b --- /dev/null +++ b/core/java/android/hardware/camera2/ExtensionCaptureResult.java @@ -0,0 +1,272 @@ +/* + * 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.hardware.camera2; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.CaptureResult; +import android.hardware.camera2.CaptureResult.Key; +import android.hardware.camera2.impl.ExtensionKey; +import android.hardware.camera2.impl.PublicKey; + +import com.android.internal.camera.flags.Flags; + +/** + * ExtensionCaptureResult contains definitions for extension-specific CaptureResult keys that + * are available during a {@link android.hardware.camera2.CameraExtensionSession} after a + * {@link android.hardware.camera2.CaptureRequest} is processed. + * + * Note that ExtensionCaptureResult is not intended to be used as a replacement + * for CaptureResult in the extensions. It serves as a supplementary class providing + * extension-specific CaptureResult keys. Developers should use these keys in conjunction + * with regular CaptureResult objects during a + * {@link android.hardware.camera2.CameraExtensionSession}. + * + * @see CaptureResult + * @see CaptureRequest + * @see CameraExtensionSession + */ +@FlaggedApi(Flags.FLAG_CONCERT_MODE) +public final class ExtensionCaptureResult { + + /** + * <p>The padding region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>The padding region denotes the area surrounding the stabilized target region within which + * the camera can be moved while maintaining the target region in view. As the camera moves, + * the padding region adjusts to represent the proximity of the target region to the + * boundary, which is the point at which the target region will start to go out of bounds.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction. + * The pixels reference the active array coordinate system. Negative values indicate the target region + * is out of bounds. The value for this key may be null for when the stabilization mode is + * in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_OFF } + * or {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_GIMBAL } mode.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_PADDING_REGION = CaptureResult.EFV_PADDING_REGION; + + /** + * <p>The padding region when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>An array [left, top, right, bottom] of the padding in pixels remaining on all four sides + * before the target region starts to go out of bounds.</p> + * <p>This may differ from {@link ExtensionCaptureResult#EFV_PADDING_REGION } as the field of view can change + * during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }, altering the boundary region and thus updating the padding between the + * target region and the boundary.</p> + * <p><b>Range of valid values:</b><br> + * The padding is the number of remaining pixels of padding in each direction + * when {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled. Negative values indicate the target region is out of bounds. + * The value for this key may be null for when the {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is not enabled.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureResult#EFV_PADDING_REGION + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<int[]> EFV_AUTO_ZOOM_PADDING_REGION = CaptureResult.EFV_AUTO_ZOOM_PADDING_REGION; + + /** + * <p>List of coordinates representing the target region relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE } + * for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A list of android.graphics.PointF that define the coordinates of the target region + * relative to the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }. + * The array represents the target region coordinates as: top-left, top-right, bottom-left, + * bottom-right.</p> + * <p><b>Range of valid values:</b><br> + * The list of target coordinates will define a region within the bounds of the + * {@link android.hardware.camera2.CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.graphics.PointF[]> EFV_TARGET_COORDINATES = CaptureResult.EFV_TARGET_COORDINATES; + + /** + * <p>Used to apply an additional digital zoom factor for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>For the {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature, an additional zoom factor is applied on top of the existing {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}. + * This additional zoom factor serves as a buffer to provide more flexibility for the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } + * mode. If {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } is not set, the default will be used. + * The effectiveness of the stabilization may be influenced by the amount of padding zoom + * applied. A higher padding zoom factor can stabilize the target region more effectively + * with greater flexibility but may potentially impact image quality. Conversely, a lower + * padding zoom factor may be used to prioritize preserving image quality, albeit with less + * leeway in stabilizing the target region. It is recommended to set the + * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to at least 1.5.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, the requested {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } will be overridden. + * {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR } can be checked for more details on controlling the + * padding zoom factor during {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM }.</p> + * <p><b>Range of valid values:</b><br> + * {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_PADDING_ZOOM_FACTOR = CaptureResult.EFV_PADDING_ZOOM_FACTOR; + + /** + * <p>Set the stabilization mode for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension</p> + * <p>The desired stabilization mode. Gimbal stabilization mode provides simple, non-locked + * video stabilization. Locked mode uses the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * stabilization feature to fixate on the current region, utilizing it as the target area for + * stabilization.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EFV_STABILIZATION_MODE_OFF OFF}</li> + * <li>{@link #EFV_STABILIZATION_MODE_GIMBAL GIMBAL}</li> + * <li>{@link #EFV_STABILIZATION_MODE_LOCKED LOCKED}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EFV_STABILIZATION_MODE_OFF + * @see #EFV_STABILIZATION_MODE_GIMBAL + * @see #EFV_STABILIZATION_MODE_LOCKED + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Integer> EFV_STABILIZATION_MODE = CaptureResult.EFV_STABILIZATION_MODE; + + /** + * <p>Used to enable or disable auto zoom for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Turn on auto zoom to let the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * feature decide at any given point a combination of + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} and {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } + * to keep the target region in view and stabilized. The combination chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * will equal the requested {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} multiplied with the requested + * {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR }. A limit can be set on the padding zoom if wanting + * to control image quality further using {@link ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR }.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see CaptureRequest#CONTROL_ZOOM_RATIO + * @see ExtensionCaptureRequest#EFV_MAX_PADDING_ZOOM_FACTOR + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Boolean> EFV_AUTO_ZOOM = CaptureResult.EFV_AUTO_ZOOM; + + /** + * <p>Representing the desired clockwise rotation + * of the target region in degrees for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>Value representing the desired clockwise rotation of the target + * region in degrees.</p> + * <p><b>Range of valid values:</b><br> + * 0 to 360</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_ROTATE_VIEWPORT = CaptureResult.EFV_ROTATE_VIEWPORT; + + /** + * <p>Used to update the target region for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>A android.util.Pair<Integer,Integer> that represents the desired + * <Horizontal,Vertical> shift of the current locked view (or target region) in + * pixels. Negative values indicate left and upward shifts, while positive values indicate + * right and downward shifts in the active array coordinate system.</p> + * <p><b>Range of valid values:</b><br> + * android.util.Pair<Integer,Integer> represents the + * <Horizontal,Vertical> shift. The range for the horizontal shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-left), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-right)]. + * The range for the vertical shift is + * [-max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-top), max({@link ExtensionCaptureResult#EFV_PADDING_REGION }-bottom)]</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureResult#EFV_PADDING_REGION + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<android.util.Pair<Integer,Integer>> EFV_TRANSLATE_VIEWPORT = CaptureResult.EFV_TRANSLATE_VIEWPORT; + + /** + * <p>Used to limit the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } if + * {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled for the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode.</p> + * <p>If {@link ExtensionCaptureRequest#EFV_AUTO_ZOOM } is enabled, this key can be used to set a limit + * on the {@link ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } chosen by the + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY } + * extension in {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_STABILIZATION_MODE_LOCKED } mode + * to control image quality.</p> + * <p><b>Range of valid values:</b><br> + * The range of {@link CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE }. Use a value greater than or equal to + * the {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } to + * effectively utilize this key.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * + * @see ExtensionCaptureRequest#EFV_AUTO_ZOOM + * @see ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR + * @see CameraExtensionCharacteristics#EFV_PADDING_ZOOM_FACTOR_RANGE + */ + @PublicKey + @NonNull + @ExtensionKey + @FlaggedApi(Flags.FLAG_CONCERT_MODE) + public static final Key<Float> EFV_MAX_PADDING_ZOOM_FACTOR = CaptureResult.EFV_MAX_PADDING_ZOOM_FACTOR; + +}
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/impl/ExtensionKey.java b/core/java/android/hardware/camera2/impl/ExtensionKey.java new file mode 100644 index 000000000000..15e8982c12b4 --- /dev/null +++ b/core/java/android/hardware/camera2/impl/ExtensionKey.java @@ -0,0 +1,36 @@ +/* + * 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.hardware.camera2.impl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denote a static field {@code Key} as being an extension key (i.e. @hide as a CaptureRequest/ + * CaptureResult key but exposed as a @PublicKey through + * ExtensionCaptureRequest/ExtensionCaptureResult). + * + * <p>Keys with this annotation are assumed to always have a hidden key counter-part in + * CaptureRequest/CaptureResult.</p> + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface ExtensionKey { + +} diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 09f5f36187d4..c0e506b05a64 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -186,6 +186,10 @@ interface IUsbManager /* Gets the status of the specified USB port. */ UsbPortStatus getPortStatus(in String portId); + /* Returns if the specified USB port supports mode change. */ + @EnforcePermission("MANAGE_USB") + boolean isModeChangeSupported(in String portId); + /* Sets the port's current role. */ void setPortRoles(in String portId, int powerRole, int dataRole); diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 81a023451f16..41f344a03e77 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -1474,6 +1474,21 @@ public class UsbManager { } /** + * Checks if the given port supports mode change. Should only be called by + * {@link UsbPort#isModeChangeSupported}. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + boolean isModeChangeSupported(UsbPort port) { + try { + return mService.isModeChangeSupported(port.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Should only be called by {@link UsbPort#setRoles}. * * @hide diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index 8f0149b39b9a..03398c45e3b2 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -65,11 +65,14 @@ import static android.hardware.usb.DisplayPortAltModeInfo.DISPLAYPORT_ALT_MODE_S import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.CheckResult; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.hardware.usb.flags.Flags; import android.hardware.usb.UsbOperationInternal; import android.hardware.usb.V1_0.Constants; import android.os.Binder; @@ -375,6 +378,19 @@ public final class UsbPort { } /** + * Returns whether this USB port supports mode change + * + * @return true if mode change is supported. + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_USB) + @FlaggedApi(Flags.FLAG_ENABLE_IS_MODE_CHANGE_SUPPORTED_API) + public boolean isModeChangeSupported() { + return mUsbManager.isModeChangeSupported(this); + } + + /** * Queries USB Port to see if the port is capable of identifying * non compliant USB power source/cable/accessory. * diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig index cc56a311e9a4..a4956311995c 100644 --- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig +++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig @@ -6,3 +6,10 @@ flag { description: "Feature flag for the api to check if a port is PD compliant" bug: "323470419" } + +flag { + name: "enable_is_mode_change_supported_api" + namespace: "usb" + description: "Feature flag for the api to check if a port supports mode change" + bug: "323470419" +} diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index f2ef185a0500..f7b417337911 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -21,7 +21,6 @@ import android.annotation.IntDef; import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.admin.flags.Flags; -import android.compat.annotation.UnsupportedAppUsage; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -127,12 +126,8 @@ public final class BugreportParams { /** * Options for a lightweight bugreport intended to be taken for onboarding-related flows. - * - * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED) - @UnsupportedAppUsage public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING; /** @@ -180,10 +175,7 @@ public final class BugreportParams { * The bugreport may be retrieved multiple times using * {@link BugreportManager#retrieveBugreport( * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}. - * - * @hide */ - @TestApi @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED) public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = IDumpstate.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL; diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index fbec518e4a29..3950c25675d8 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -42,7 +42,7 @@ import java.util.ArrayList; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.MessageQueue_host") + "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host") public final class MessageQueue { private static final String TAG = "MessageQueue"; private static final boolean DEBUG = false; diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 8e860c35388d..ccfb6326d941 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -233,7 +233,8 @@ import java.util.function.IntFunction; * {@link #readSparseArray(ClassLoader, Class)}. */ @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.Parcel_host") +@RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.Parcel_host") public final class Parcel { private static final boolean DEBUG_RECYCLE = false; diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 6532d5c8784a..17dfdda7dffc 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -75,7 +75,8 @@ import java.nio.ByteOrder; * you to close it when done with it. */ @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.ParcelFileDescriptor_host") +@RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.ParcelFileDescriptor_host") public class ParcelFileDescriptor implements Parcelable, Closeable { private static final String TAG = "ParcelFileDescriptor"; diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 07f76904988a..b669814a8962 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -1418,8 +1418,11 @@ public class RecoverySystem { * @throws IOException if the recovery system service could not be contacted */ private boolean requestLskf(String packageName, IntentSender sender) throws IOException { + Log.i(TAG, String.format("<%s> is requesting LSFK", packageName)); try { - return mService.requestLskf(packageName, sender); + boolean validRequest = mService.requestLskf(packageName, sender); + Log.i(TAG, String.format("LSKF Request isValid = %b", validRequest)); + return validRequest; } catch (RemoteException | SecurityException e) { throw new IOException("could not request LSKF capture", e); } diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index a818919d184e..0a386913de59 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -56,7 +56,8 @@ import java.util.function.Predicate; */ @SystemApi @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host") +@RavenwoodNativeSubstitutionClass( + "com.android.platform.test.ravenwood.nativesubstitution.SystemProperties_host") public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 89576ed62afe..2b30a2ba2d4e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -163,19 +163,16 @@ public class UserManager { * User type representing a managed profile, which is a profile that is to be managed by a * device policy controller (DPC). * The intended purpose is for work profiles, which are managed by a corporate entity. - * @hide */ - @SystemApi + @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE) public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED"; /** * User type representing a clone profile. Clone profile is a user profile type used to run * second instance of an otherwise single user App (eg, messengers). Currently only the * {@link android.content.pm.UserInfo#isMain()} user can have a clone profile. - * - * @hide */ - @SystemApi + @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE) public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; @@ -184,10 +181,8 @@ public class UserManager { * as an alternative user-space to install and use sensitive apps. * UI surfaces can adopt an alternative strategy to show apps belonging to this profile, in line * with their sensitive nature. - * @hide */ @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE) - @SystemApi public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE"; /** @@ -1785,7 +1780,11 @@ public class UserManager { /** * Specifies whether the user is allowed to modify default apps in settings. * - * <p>This restriction can be set by device or profile owner. + * <p>A device owner and a profile owner can set this restriction. When it is set by a + * device owner, it applies globally - i.e., modifying of default apps in Settings for all + * users is disallowed. When it is set by a profile owner on the primary user or by a profile + * owner of an organization-owned managed profile on the parent profile, modifying of + * default apps in Settings for the primary user is disallowed. * * <p>The default value is <code>false</code>. * @@ -3259,7 +3258,11 @@ public class UserManager { return isProfile(mUserId); } - private boolean isProfile(@UserIdInt int userId) { + /** + * Returns whether the specified user is a profile. + * @hide + */ + public boolean isProfile(@UserIdInt int userId) { final String profileType = getProfileType(userId); return profileType != null && !profileType.equals(""); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ecf193739627..ec4d5876070a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1551,6 +1551,23 @@ public final class Settings { "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; /** + * Activity Action: Show screen for controlling any background restrictions imposed on + * an app. If the system returns true for + * {@link android.app.ActivityManager#isBackgroundRestricted()}, and the app is not able to + * satisfy user requests due to being restricted in the background, then this intent can be + * used to request the user to unrestrict the app. + * <p> + * Input: The Intent's data URI must specify the application package name + * to be shown, with the "package" scheme, such as "package:com.my.app". + * <p> + * Output: Nothing. + */ + @FlaggedApi(android.app.Flags.FLAG_APP_RESTRICTIONS_API) + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_BACKGROUND_RESTRICTIONS_SETTINGS = + "android.settings.BACKGROUND_RESTRICTIONS_SETTINGS"; + + /** * Activity Action: Open the advanced power usage details page of an associated app. * <p> * Input: Intent's data URI set with an application name, using the diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 43163b3b9051..76314546b4f0 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -15,10 +15,11 @@ flag { } flag { - name: "mgf1_digest_setter" + name: "mgf1_digest_setter_v2" namespace: "hardware_backed_security" description: "Feature flag for mgf1 digest setter in key generation and import parameters." bug: "308378912" + is_fixed_read_only: true } flag { diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 786d768bc55b..aa47d3a5c2af 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -673,6 +673,10 @@ public final class ZenPolicy implements Parcelable { mZenPolicy.mPriorityMessages = PEOPLE_TYPE_NONE; mZenPolicy.mPriorityCalls = PEOPLE_TYPE_NONE; mZenPolicy.mConversationSenders = CONVERSATION_SENDERS_NONE; + + if (Flags.modesApi()) { + mZenPolicy.mAllowChannels = CHANNEL_POLICY_NONE; + } return this; } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 94d851603064..a08264e625df 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -22,6 +22,7 @@ import static android.service.voice.SoundTriggerFailure.ERROR_CODE_UNKNOWN; import static android.service.voice.VoiceInteractionService.MULTIPLE_ACTIVE_HOTWORD_DETECTORS; import android.annotation.ElapsedRealtimeLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -432,7 +433,10 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @ElapsedRealtimeLong private final long mHalEventReceivedMillis; - private EventPayload(boolean captureAvailable, + private final boolean mIsRecognitionStopped; + + private EventPayload( + boolean captureAvailable, @Nullable AudioFormat audioFormat, int captureSession, @DataFormat int dataFormat, @@ -440,7 +444,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { @Nullable HotwordDetectedResult hotwordDetectedResult, @Nullable ParcelFileDescriptor audioStream, @NonNull List<KeyphraseRecognitionExtra> keyphraseExtras, - @ElapsedRealtimeLong long halEventReceivedMillis) { + @ElapsedRealtimeLong long halEventReceivedMillis, + boolean isRecognitionStopped) { mCaptureAvailable = captureAvailable; mCaptureSession = captureSession; mAudioFormat = audioFormat; @@ -450,6 +455,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { mAudioStream = audioStream; mKephraseExtras = keyphraseExtras; mHalEventReceivedMillis = halEventReceivedMillis; + mIsRecognitionStopped = isRecognitionStopped; } /** @@ -592,6 +598,12 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { return mHalEventReceivedMillis; } + /** Returns whether the system has stopped hotword recognition because of this detection. */ + @FlaggedApi(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + public boolean isRecognitionStopped() { + return mIsRecognitionStopped; + } + /** * Builder class for {@link EventPayload} objects * @@ -610,6 +622,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { private List<KeyphraseRecognitionExtra> mKeyphraseExtras = Collections.emptyList(); @ElapsedRealtimeLong private long mHalEventReceivedMillis = -1; + // default to true to keep prior behavior + private boolean mIsRecognitionStopped = true; public Builder() {} @@ -746,13 +760,31 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } /** + * Sets whether the system has stopped hotword recognition because of this detection. + */ + @FlaggedApi(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + @NonNull + public Builder setIsRecognitionStopped(boolean isRecognitionStopped) { + mIsRecognitionStopped = isRecognitionStopped; + return this; + } + + /** * Builds an {@link EventPayload} instance */ @NonNull public EventPayload build() { - return new EventPayload(mCaptureAvailable, mAudioFormat, mCaptureSession, - mDataFormat, mData, mHotwordDetectedResult, mAudioStream, - mKeyphraseExtras, mHalEventReceivedMillis); + return new EventPayload( + mCaptureAvailable, + mAudioFormat, + mCaptureSession, + mDataFormat, + mData, + mHotwordDetectedResult, + mAudioStream, + mKeyphraseExtras, + mHalEventReceivedMillis, + mIsRecognitionStopped); } } } @@ -786,14 +818,20 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { /** * Called when the keyphrase is spoken. - * This implicitly stops listening for the keyphrase once it's detected. - * Clients should start a recognition again once they are done handling this - * detection. * - * @param eventPayload Payload data for the detection event. - * This may contain the trigger audio, if requested when calling - * {@link AlwaysOnHotwordDetector#startRecognition(int)}. + * <p>This implicitly stops listening for the keyphrase once it's detected. Clients should + * start a recognition again once they are done handling this detection. + * + * @param eventPayload Payload data for the detection event. This may contain the trigger + * audio, if requested when calling {@link + * AlwaysOnHotwordDetector#startRecognition(int)}. */ + // TODO(b/324635656): Update Javadoc for 24Q3 release: + // 1. Prepend to the first paragraph: + // If {@code eventPayload.isRecognitionStopped()} returns true, this... + // 2. Append to the description for @param eventPayload: + // ...or if the audio comes from {@link + // android.service.wearable.WearableSensingService}. public abstract void onDetected(@NonNull EventPayload eventPayload); /** @@ -1632,6 +1670,20 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @Override + public void onKeyphraseDetectedFromExternalSource(HotwordDetectedResult result) { + Slog.i(TAG, "onKeyphraseDetectedFromExternalSource"); + EventPayload.Builder eventPayloadBuilder = new EventPayload.Builder(); + if (android.app.wearable.Flags.enableHotwordWearableSensingApi()) { + eventPayloadBuilder.setIsRecognitionStopped(false); + } + Message.obtain( + mHandler, + MSG_HOTWORD_DETECTED, + eventPayloadBuilder.setHotwordDetectedResult(result).build()) + .sendToTarget(); + } + + @Override public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { Slog.w(TAG, "Generic sound trigger event detected at AOHD: " + event); } diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java index ccf8b67826c8..60e9de72f154 100644 --- a/core/java/android/service/voice/HotwordDetectionService.java +++ b/core/java/android/service/voice/HotwordDetectionService.java @@ -19,6 +19,7 @@ package android.service.voice; import static java.util.Objects.requireNonNull; import android.annotation.DurationMillisLong; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -79,6 +80,16 @@ public abstract class HotwordDetectionService extends Service private static final long UPDATE_TIMEOUT_MILLIS = 20000; /** + * The PersistableBundle options key used in {@link #onDetect(ParcelFileDescriptor, AudioFormat, + * PersistableBundle, Callback)} to indicate whether the system will close the audio stream + * after {@code Callback} is invoked. + */ + @FlaggedApi(android.app.wearable.Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + public static final String KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK = + "android.service.voice.HotwordDetectionService." + + "KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK"; + + /** * Feature flag for Attention Service. * * @hide @@ -364,6 +375,11 @@ public abstract class HotwordDetectionService extends Service * PersistableBundle)}. * @param callback The callback to use for responding to the detection request. */ + // TODO(b/324635656): Update Javadoc for 24Q3 release. Change the last paragraph to: + // <p>Upon invoking the {@code callback}, the system will send the detection result to + // the {@link HotwordDetector}'s callback. If {@code + // options.getBoolean(KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, true)} returns true, + // the system will also close the {@code audioStream} after {@code callback} is invoked. public void onDetect( @NonNull ParcelFileDescriptor audioStream, @NonNull AudioFormat audioFormat, diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java index f1bc792696d6..a835b0f57998 100644 --- a/core/java/android/service/voice/SoftwareHotwordDetector.java +++ b/core/java/android/service/voice/SoftwareHotwordDetector.java @@ -223,6 +223,13 @@ class SoftwareHotwordDetector extends AbstractDetector { } @Override + public void onKeyphraseDetectedFromExternalSource(HotwordDetectedResult result) { + if (DEBUG) { + Slog.i(TAG, "Ignored #onKeyphraseDetectedFromExternalSource event"); + } + } + + @Override public void onGenericSoundTriggerDetected( SoundTrigger.GenericRecognitionEvent recognitionEvent) throws RemoteException { if (DEBUG) { diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index 23847fe76ecc..1eb4d6730e1e 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -414,6 +414,13 @@ public class VisualQueryDetector { } @Override + public void onKeyphraseDetectedFromExternalSource(HotwordDetectedResult result) { + if (DEBUG) { + Slog.i(TAG, "Ignored #onKeyphraseDetectedFromExternalSource event"); + } + } + + @Override public void onGenericSoundTriggerDetected( SoundTrigger.GenericRecognitionEvent recognitionEvent) throws RemoteException { if (DEBUG) { diff --git a/core/java/android/service/voice/VoiceInteractionManagerInternal.java b/core/java/android/service/voice/VoiceInteractionManagerInternal.java index 270f848598d7..7d2773387c1a 100644 --- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java +++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java @@ -19,14 +19,17 @@ package android.service.voice; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; +import android.content.ComponentName; +import android.media.AudioFormat; import android.os.Bundle; import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; import com.android.internal.annotations.Immutable; /** - * @hide - * Private interface to the VoiceInteractionManagerService for use by ActivityManagerService. + * @hide Private interface to the VoiceInteractionManagerService for use within system_server. */ public abstract class VoiceInteractionManagerInternal { @@ -77,6 +80,25 @@ public abstract class VoiceInteractionManagerInternal { public abstract void onPreCreatedUserConversion(@UserIdInt int userId); /** + * Called by {@link com.android.server.wearable.WearableSensingManagerPerUserService} when a + * wearable starts sending audio data for hotword detection. + * + * @param audioStream The audio data. + * @param audioFormat The format of the audio data. + * @param options Options supporting hotword detection. + * @param targetVisComponentName The target VoiceInteractionService ComponentName + * @param userId The user ID of the calling wearable service + * @param callback The callback to notify the caller of the hotword detection result. + */ + public abstract void startListeningFromWearable( + ParcelFileDescriptor audioStream, + AudioFormat audioFormat, + PersistableBundle options, + ComponentName targetVisComponentName, + int userId, + WearableHotwordDetectionCallback callback); + + /** * Provides the uids of the currently active * {@link android.service.voice.HotwordDetectionService} and its owning package. The * HotwordDetectionService is an isolated service, so it has a separate uid. @@ -101,4 +123,20 @@ public abstract class VoiceInteractionManagerInternal { return mOwnerUid; } } + + /** + * Callback for returning the detected hotword result to the wearable. + * + * @hide + */ + public interface WearableHotwordDetectionCallback { + /** Called when hotword is detected. */ + void onDetected(); + + /** Called when hotword is not detected. */ + void onRejected(); + + /** Called when an unexpected error occurs. */ + void onError(String errorMessage); + } } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 44bcdbe28cd5..20adc5427495 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -18,7 +18,6 @@ package android.service.voice; import android.Manifest; import android.annotation.CallbackExecutor; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -49,7 +48,6 @@ import android.os.ServiceManager; import android.os.SharedMemory; import android.os.SystemProperties; import android.provider.Settings; -import android.service.voice.flags.Flags; import android.util.ArraySet; import android.util.Log; @@ -1011,36 +1009,6 @@ public class VoiceInteractionService extends Service { } /** - * Allow/disallow receiving training data from trusted process. - * - * <p> This method can be called by a preinstalled assistant to receive/stop receiving - * training data via {@link HotwordDetector.Callback#onTrainingData(HotwordTrainingData)}. - * These training data events are produced during sandboxed detection (in trusted process). - * - * @param allowed whether to allow/disallow receiving training data produced during - * sandboxed detection (from trusted process). - * @throws SecurityException if caller is not a preinstalled assistant or if caller is not the - * active assistant. - * - * @hide - */ - //TODO(b/315053245): Add mitigations to make API no-op once user has modified setting. - @SystemApi - @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS) - @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) - public void setShouldReceiveSandboxedTrainingData(boolean allowed) { - Log.i(TAG, "setShouldReceiveSandboxedTrainingData to " + allowed); - if (mSystemService == null) { - throw new IllegalStateException("Not available until onReady() is called"); - } - try { - mSystemService.setShouldReceiveSandboxedTrainingData(allowed); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Creates an {@link KeyphraseModelManager} to use for enrolling voice models outside of the * pre-bundled system voice models. * @hide diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl index f67dcff3ebfe..22d8fda9cb8e 100644 --- a/core/java/android/service/wearable/IWearableSensingService.aidl +++ b/core/java/android/service/wearable/IWearableSensingService.aidl @@ -33,6 +33,10 @@ oneway interface IWearableSensingService { void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback); void registerDataRequestObserver(int dataType, in RemoteCallback dataRequestCallback, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback); void unregisterDataRequestObserver(int dataType, int dataRequestObserverId, in String packageName, in RemoteCallback statusCallback); + void startHotwordRecognition(in RemoteCallback wearableHotwordCallback, in RemoteCallback statusCallback); + void stopHotwordRecognition(in RemoteCallback statusCallback); + void onValidatedByHotwordDetectionService(); + void stopActiveHotwordAudio(); void startDetection(in AmbientContextEventRequest request, in String packageName, in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback); void stopDetection(in String packageName); diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java index bb6e030456a7..808c3ae7b6bc 100644 --- a/core/java/android/service/wearable/WearableSensingService.java +++ b/core/java/android/service/wearable/WearableSensingService.java @@ -37,6 +37,7 @@ import android.os.RemoteCallback; import android.os.SharedMemory; import android.service.ambientcontext.AmbientContextDetectionResult; import android.service.ambientcontext.AmbientContextDetectionServiceStatus; +import android.service.voice.HotwordAudioStream; import android.util.Slog; import android.util.SparseArray; @@ -85,6 +86,14 @@ public abstract class WearableSensingService extends Service { "android.app.wearable.WearableSensingStatusBundleKey"; /** + * The bundle key for hotword audio stream, used in {@code RemoteCallback#sendResult}. + * + * @hide + */ + public static final String HOTWORD_AUDIO_STREAM_BUNDLE_KEY = + "android.app.wearable.HotwordAudioStreamBundleKey"; + + /** * The {@link Intent} that must be declared as handled by the service. To be supported, the * service must also require the * {@link android.Manifest.permission#BIND_WEARABLE_SENSING_SERVICE} @@ -181,6 +190,50 @@ public abstract class WearableSensingService extends Service { dataType, packageName, dataRequester, statusConsumer); } + @Override + public void startHotwordRecognition( + RemoteCallback wearableHotwordCallback, RemoteCallback statusCallback) { + Consumer<HotwordAudioStream> hotwordAudioConsumer = + (hotwordAudioStream) -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + HOTWORD_AUDIO_STREAM_BUNDLE_KEY, hotwordAudioStream); + wearableHotwordCallback.sendResult(bundle); + }; + Consumer<Integer> statusConsumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response); + statusCallback.sendResult(bundle); + }; + WearableSensingService.this.onStartHotwordRecognition( + hotwordAudioConsumer, statusConsumer); + } + + /** {@inheritDoc} */ + @Override + public void stopHotwordRecognition(RemoteCallback statusCallback) { + Consumer<Integer> statusConsumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response); + statusCallback.sendResult(bundle); + }; + WearableSensingService.this.onStopHotwordRecognition(statusConsumer); + } + + /** {@inheritDoc} */ + @Override + public void onValidatedByHotwordDetectionService() { + WearableSensingService.this.onValidatedByHotwordDetectionService(); + } + + /** {@inheritDoc} */ + @Override + public void stopActiveHotwordAudio() { + WearableSensingService.this.onStopHotwordAudioStream(); + } + /** {@inheritDoc} */ @Override public void startDetection( @@ -377,6 +430,100 @@ public abstract class WearableSensingService extends Service { } /** + * Called when the wearable is requested to start hotword recognition. + * + * <p>This method is expected to be overridden by a derived class. The implementation should + * store the {@code hotwordAudioConsumer} and send it the audio data when first-stage hotword is + * detected from the wearable. It should also send a {@link + * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it + * encounters an error condition described by a status code listed in {@link + * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, + * in which case it should return the corresponding status code. + * + * <p>The implementation should also store the {@code statusConsumer}. If the wearable stops + * listening for hotword for any reason other than {@link #onStopListeningForHotword(Consumer)} + * being invoked, it should send an appropriate status code listed in {@link + * WearableSensingManager} to {@code statusConsumer}. If the error condition cannot be described + * by any of those status codes, it should send a {@link WearableSensingManager#STATUS_UNKNOWN}. + * + * <p>If this method is called again, the implementation should use the new {@code + * hotwordAudioConsumer} and discard any previous ones it received. + * + * <p>At this time, the {@code timestamp} field in the {@link HotwordAudioStream} is not used + * and will be discarded by the system. + * + * @param hotwordAudioConsumer The consumer for the wearable hotword audio data. + * @param statusConsumer The consumer for the service status. + */ + @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + @BinderThread + public void onStartHotwordRecognition( + @NonNull Consumer<HotwordAudioStream> hotwordAudioConsumer, + @NonNull Consumer<Integer> statusConsumer) { + if (Flags.enableUnsupportedOperationStatusCode()) { + statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); + } + } + + /** + * Called when the wearable is requested to stop hotword recognition. + * + * <p>This method is expected to be overridden by a derived class. It should send a {@link + * WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it + * encounters an error condition described by a status code listed in {@link + * WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, + * in which case it should return the corresponding status code. + * + * @param statusConsumer The consumer for the service status. + */ + @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + @BinderThread + public void onStopHotwordRecognition(@NonNull Consumer<Integer> statusConsumer) { + if (Flags.enableUnsupportedOperationStatusCode()) { + statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION); + } + } + + /** + * Called when hotword audio data sent to the {@code hotwordAudioConsumer} in {@link + * #onStartListeningForHotword(Consumer, Consumer)} is accepted by the + * {@link android.service.voice.HotwordDetectionService} as valid hotword. + * + * <p>After the implementation of this class sends the hotword audio data to the {@code + * hotwordAudioConsumer} in {@link #onStartListeningForHotword(Consumer, + * Consumer)}, the system will forward the data into {@link + * android.service.voice.HotwordDetectionService} (which runs in an isolated process) for + * second-stage hotword detection. If accepted as valid hotword there, this method will be + * called, and then the system will send the data to the currently active {@link + * android.service.voice.AlwaysOnHotwordDetector} (which may not run in an isolated process). + * + * <p>This method is expected to be overridden by a derived class. The implementation must + * request the wearable to turn on the microphone indicator to notify the user that audio data + * is being used outside of the isolated environment. + */ + @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + @BinderThread + public void onValidatedByHotwordDetectionService() {} + + /** + * Called when the currently active hotword audio stream is no longer needed. + * + * <p>This method can be called as a result of hotword rejection by {@link + * android.service.voice.HotwordDetectionService}, or the {@link + * android.service.voice.AlwaysOnHotwordDetector} closing the data stream it received, or a + * non-recoverable error occurred before the data reaches the {@link + * android.service.voice.HotwordDetectionService} or the {@link + * android.service.voice.AlwaysOnHotwordDetector}. + * + * <p>This method is expected to be overridden by a derived class. The implementation should + * stop sending hotword audio data to the {@code hotwordAudioConsumer} in {@link + * #onStartListeningForHotword(Consumer, Consumer)} + */ + @FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API) + @BinderThread + public void onStopHotwordAudioStream() {} + + /** * Called when a client app requests starting detection of the events in the request. The * implementation should keep track of whether the user has explicitly consented to detecting * the events using on-going ambient sensor (e.g. microphone), and agreed to share the @@ -460,4 +607,6 @@ public abstract class WearableSensingService extends Service { statusCallback.sendResult(bundle); }; } + + } diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java index d2c5975ea356..0a73fd1689c3 100644 --- a/core/java/android/util/EventLog.java +++ b/core/java/android/util/EventLog.java @@ -50,7 +50,7 @@ import java.util.regex.Pattern; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.EventLog_host") + "com.android.platform.test.ravenwood.nativesubstitution.EventLog_host") public class EventLog { /** @hide */ public EventLog() {} diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 31576c5c74fc..b33214dffb7e 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -73,7 +73,7 @@ import java.net.UnknownHostException; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.Log_host") + "com.android.platform.test.ravenwood.nativesubstitution.Log_host") public final class Log { /** @hide */ @IntDef({ASSERT, ERROR, WARN, INFO, DEBUG, VERBOSE}) diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index ffe0c716905d..9413f5c0868e 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -19,10 +19,11 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiThread; +import android.content.Context; import android.graphics.Rect; import android.graphics.Region; import android.hardware.HardwareBuffer; -import android.os.IBinder; +import android.os.Looper; import android.window.InputTransferToken; import android.window.SurfaceSyncGroup; @@ -180,32 +181,25 @@ public interface AttachedSurfaceControl { } /** - * Gets the token used for associating this {@link AttachedSurfaceControl} with - * {@link SurfaceControlViewHost} instances. - * - * <p>This token should be passed to {@link SurfaceControlViewHost}'s constructor. - * This token will be {@code null} if the window does not have an input channel. - * - * @return The SurfaceControlViewHost link token. - */ - @Nullable - @FlaggedApi(Flags.FLAG_GET_HOST_TOKEN_API) - default IBinder getHostToken() { - throw new UnsupportedOperationException("The getHostToken needs to be " - + "implemented before making this call."); - } - - /** * Gets the token used for associating this {@link AttachedSurfaceControl} with an embedded * {@link SurfaceControlViewHost} or {@link SurfaceControl} * - * @return The SurfaceControlViewHost link token. This can return {@code null} if the - * {@link AttachedSurfaceControl} was created with no registered input - * @hide + * <p>This token should be passed to + * {@link SurfaceControlViewHost#SurfaceControlViewHost(Context, Display, InputTransferToken)} + * or + * {@link WindowManager#registerBatchedSurfaceControlInputReceiver(int, InputTransferToken, + * SurfaceControl, Choreographer, SurfaceControlInputReceiver)} or + * {@link WindowManager#registerUnbatchedSurfaceControlInputReceiver(int, InputTransferToken, + * SurfaceControl, Looper, SurfaceControlInputReceiver)} + * + * @return The SurfaceControlViewHost link token. + * @throws IllegalStateException if the {@link AttachedSurfaceControl} was created with no + * registered input */ - @Nullable + @NonNull + @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) default InputTransferToken getInputTransferToken() { - throw new UnsupportedOperationException("The getHostToken needs to be " + throw new UnsupportedOperationException("The getInputTransferToken needs to be " + "implemented before making this call."); } diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index 58765b46a05a..1dd9cbb76a9f 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -33,6 +34,8 @@ import android.window.ISurfaceSyncGroup; import android.window.InputTransferToken; import android.window.WindowTokenClient; +import com.android.window.flags.Flags; + import dalvik.system.CloseGuard; import java.util.Objects; @@ -347,6 +350,25 @@ public class SurfaceControlViewHost { @Nullable IBinder hostToken) { this(context, display, hostToken == null ? null : new InputTransferToken(hostToken), "untracked"); + + } + + /** + * Construct a new SurfaceControlViewHost. The root Surface will be + * allocated internally and is accessible via getSurfacePackage(). + * <p> + * The hostInputTransferToken parameter allows the host and embedded to be associated with + * each other to allow transferring touch gesture and focus. This is also used for ANR + * reporting. It's accessible from {@link AttachedSurfaceControl#getInputTransferToken()}. + * + * @param context The Context object for your activity or application. + * @param display The Display the hierarchy will be placed on. + * @param hostInputTransferToken The host input transfer token, as discussed above. + */ + @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) + public SurfaceControlViewHost(@NonNull Context context, @NonNull Display display, + @Nullable InputTransferToken hostInputTransferToken) { + this(context, display, hostInputTransferToken, "untracked"); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 254c4aed57c1..25ade62078fd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -18,6 +18,7 @@ package android.view; import static android.content.res.Resources.ID_NULL; import static android.os.Trace.TRACE_TAG_APP; +import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; @@ -6995,7 +6996,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #setCredentialManagerRequest */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public void clearCredentialManagerRequest() { if (Log.isLoggable(AUTOFILL_LOG_TAG, Log.VERBOSE)) { Log.v(AUTOFILL_LOG_TAG, "clearCredentialManagerRequest called"); @@ -7027,7 +7028,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param callback to be invoked when either a response or an exception needs to be * propagated for the given view */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public void setCredentialManagerRequest(@NonNull GetCredentialRequest request, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) { Preconditions.checkNotNull(request, "request must not be null"); @@ -9944,7 +9945,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @return The credential request associated with this View. */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable public final GetCredentialRequest getCredentialManagerRequest() { if (mViewCredentialHandler == null) { @@ -9968,7 +9969,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return The callback associated with this view that will be invoked on a response from * {@link CredentialManager} . */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable public final OutcomeReceiver<GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 28a73344b731..657c8e644f3a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -103,7 +103,6 @@ import android.accessibilityservice.AccessibilityService; import android.animation.AnimationHandler; import android.animation.LayoutTransition; import android.annotation.AnyThread; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Size; @@ -240,7 +239,6 @@ import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.SurfaceCallbackHelper; import com.android.modules.expresslog.Counter; -import com.android.window.flags.Flags; import java.io.IOException; import java.io.OutputStream; @@ -11220,25 +11218,15 @@ public final class ViewRootImpl implements ViewParent, } /** - * @return Returns a token used for associating the root surface - * to {@link SurfaceControlViewHost}. - */ - @Nullable - @Override - @FlaggedApi(Flags.FLAG_GET_HOST_TOKEN_API) - public IBinder getHostToken() { - return getInputToken(); - } - - /** * {@inheritDoc} */ - @Nullable + @NonNull @Override public InputTransferToken getInputTransferToken() { IBinder inputToken = getInputToken(); if (inputToken == null) { - return null; + throw new IllegalStateException( + "Called getInputTransferToken for Window with no input channel"); } return new InputTransferToken(inputToken); } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index d86cc4ac781d..131fca7d923a 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -16,6 +16,8 @@ package android.view; +import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION; + import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -361,7 +363,7 @@ public abstract class ViewStructure { * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)} */ @Nullable - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public GetCredentialRequest getCredentialManagerRequest() { return null; } @@ -376,7 +378,7 @@ public abstract class ViewStructure { * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)} */ @Nullable - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public OutcomeReceiver< GetCredentialResponse, GetCredentialException> getCredentialManagerCallback() { return null; @@ -551,7 +553,7 @@ public abstract class ViewStructure { * @param request the request to be fired * @param callback the callback where the response or exception, is returned */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public void setCredentialManagerRequest(@NonNull GetCredentialRequest request, @NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {} @@ -559,7 +561,7 @@ public abstract class ViewStructure { * Clears the credential request previously set through * {@link ViewStructure#setCredentialManagerRequest(GetCredentialRequest, OutcomeReceiver)} */ - @FlaggedApi("autofill_credman_dev_integration") + @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) public void clearCredentialManagerRequest() {} /** diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 58fb273212f7..0302a0df35c0 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -123,6 +123,7 @@ import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityNodeInfo; +import android.window.InputTransferToken; import android.window.TaskFpsCallback; import android.window.TrustedPresentationThresholds; @@ -6097,25 +6098,28 @@ public interface WindowManager extends ViewManager { * receive batched input event. For those events that are batched, the invocation will happen * once per {@link Choreographer} frame, and other input events will be delivered immediately. * This is different from - * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, - * SurfaceControlInputReceiver)} in that the input events are received batched. The caller must - * invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the - * resources when no longer needing to use the {@link SurfaceControlInputReceiver} - * - * @param displayId The display that the SurfaceControl will be placed on. Input will - * only work - * if SurfaceControl is on that display and that display was touched. - * @param surfaceControl The SurfaceControl to register the InputChannel for - * @param hostToken The host token to link the InputChannel for. This is primarily for ANRs - * to ensure the host receives the ANR if any issues with touch on the - * InputChannel - * @param choreographer The Choreographer used for batching. This should match the rendering - * Choreographer. - * @param receiver The SurfaceControlInputReceiver that will receive the input events + * { #registerUnbatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, + * Looper, SurfaceControlInputReceiver)} in that the input events are received batched. The + * caller must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up + * the resources when no longer needing to use the {@link SurfaceControlInputReceiver} + * + * @param displayId The display that the SurfaceControl will be placed on. Input + * will only work if SurfaceControl is on that display and that + * display was touched. + * @param surfaceControl The SurfaceControl to register the InputChannel for + * @param hostInputTransferToken The host token to link the embedded. This is used to handle + * transferring touch gesture from host to embedded and for ANRs + * to ensure the host receives the ANR if any issues with + * touch on the embedded. + * @param choreographer The Choreographer used for batching. This should match the + * rendering Choreographer. + * @param receiver The SurfaceControlInputReceiver that will receive the input + * events */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) default void registerBatchedSurfaceControlInputReceiver(int displayId, - @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, + @NonNull InputTransferToken hostInputTransferToken, + @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { throw new UnsupportedOperationException( "registerBatchedSurfaceControlInputReceiver is not implemented"); @@ -6123,26 +6127,30 @@ public interface WindowManager extends ViewManager { /** * Registers a {@link SurfaceControlInputReceiver} for a {@link SurfaceControl} that will - * receive every input event. This is different than calling @link - * #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, - * SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller - * must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the - * resources when no longer needing to use the {@link SurfaceControlInputReceiver} - * - * @param displayId The display that the SurfaceControl will be placed on. Input will only - * work if SurfaceControl is on that display and that display was - * touched. - * @param hostToken The host token to link the InputChannel for. This is primarily for ANRs - * to ensure the host receives the ANR if any issues with touch on the - * InputChannel - * @param surfaceControl The SurfaceControl to register the InputChannel for - * @param looper The looper to use when invoking callbacks. - * @param receiver The SurfaceControlInputReceiver that will receive the input events - **/ + * receive every input event. This is different than calling + * {@link #registerBatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, + * Choreographer, SurfaceControlInputReceiver)} in that the input events are received + * unbatched. + * The caller must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to + * clean up the resources when no longer needing to use the {@link SurfaceControlInputReceiver} + * + * @param displayId The display that the SurfaceControl will be placed on. Input + * will only work if SurfaceControl is on that display and that + * display was touched. + * @param surfaceControl The SurfaceControl to register the InputChannel for + * @param hostInputTransferToken The host token to link the embedded. This is used to handle + * transferring touch gesture from host to embedded and for ANRs + * to ensure the host receives the ANR if any issues with + * touch on the embedded. + * @param looper The looper to use when invoking callbacks. + * @param receiver The SurfaceControlInputReceiver that will receive the input + * events. + */ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) default void registerUnbatchedSurfaceControlInputReceiver(int displayId, - @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, - @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { + @NonNull InputTransferToken hostInputTransferToken, + @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, + @NonNull SurfaceControlInputReceiver receiver) { throw new UnsupportedOperationException( "registerUnbatchedSurfaceControlInputReceiver is not implemented"); } @@ -6152,10 +6160,10 @@ public interface WindowManager extends ViewManager { * specified token. * <p> * Must be called on the same {@link Looper} thread to which was passed to the - * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, + * {@link #registerBatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, * Choreographer, * SurfaceControlInputReceiver)} or - * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, + * {@link #registerUnbatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, Looper, * SurfaceControlInputReceiver)} * * @param surfaceControl The SurfaceControl to remove and unregister the input channel for. @@ -6171,7 +6179,7 @@ public interface WindowManager extends ViewManager { * if the SurfaceControl was registered for input via * { #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer, * SurfaceControlInputReceiver)} or - * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper, + * {@link #registerUnbatchedSurfaceControlInputReceiver(int, InputTransferToken, SurfaceControl, Looper, * SurfaceControlInputReceiver)}. * <p> * This is helpful for testing to ensure the test waits for the layer to be registered with diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 2fb5213279a6..1e3d0624a95b 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -535,19 +535,19 @@ public final class WindowManagerImpl implements WindowManager { @Override public void registerBatchedSurfaceControlInputReceiver(int displayId, - @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, - @NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) { - mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, - new InputTransferToken(hostToken), + @NonNull InputTransferToken hostInputTransferToken, + @NonNull SurfaceControl surfaceControl, @NonNull Choreographer choreographer, + @NonNull SurfaceControlInputReceiver receiver) { + mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostInputTransferToken, surfaceControl, choreographer, receiver); } @Override - public void registerUnbatchedSurfaceControlInputReceiver( - int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl, - @NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) { - mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, - new InputTransferToken(hostToken), + public void registerUnbatchedSurfaceControlInputReceiver(int displayId, + @NonNull InputTransferToken hostInputTransferToken, + @NonNull SurfaceControl surfaceControl, @NonNull Looper looper, + @NonNull SurfaceControlInputReceiver receiver) { + mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostInputTransferToken, surfaceControl, looper, receiver); } diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index fa0052cf664a..749f977f5e50 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -93,6 +94,12 @@ public final class AccessibilityWindowInfo implements Parcelable { */ public static final int TYPE_MAGNIFICATION_OVERLAY = 6; + /** + * Window type: A system window that has the function to control an associated window. + */ + @FlaggedApi(Flags.FLAG_ADD_TYPE_WINDOW_CONTROL) + public static final int TYPE_WINDOW_CONTROL = 7; + /* Special values for window IDs */ /** @hide */ public static final int ACTIVE_WINDOW_ID = Integer.MAX_VALUE; @@ -873,6 +880,10 @@ public final class AccessibilityWindowInfo implements Parcelable { * @hide */ public static String typeToString(int type) { + if (Flags.addTypeWindowControl() && type == TYPE_WINDOW_CONTROL) { + return "TYPE_WINDOW_CONTROL"; + } + switch (type) { case TYPE_APPLICATION: { return "TYPE_APPLICATION"; diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index a11ac7cb48ad..5b99c71f3a8b 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -102,6 +102,13 @@ flag { flag { namespace: "accessibility" + name: "add_type_window_control" + description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations" + bug: "320445550" +} + +flag { + namespace: "accessibility" name: "update_always_on_a11y_service" description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut." bug: "298869916" diff --git a/core/java/android/view/textclassifier/TextClassificationConstants.java b/core/java/android/view/textclassifier/TextClassificationConstants.java index 5f3159cd09c8..d0ed8eef7749 100644 --- a/core/java/android/view/textclassifier/TextClassificationConstants.java +++ b/core/java/android/view/textclassifier/TextClassificationConstants.java @@ -22,6 +22,8 @@ import android.provider.DeviceConfig; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; +import java.util.Optional; + /** * TextClassifier specific settings. * @@ -36,43 +38,35 @@ import com.android.internal.util.IndentingPrintWriter; */ // TODO: Rename to TextClassifierSettings. public final class TextClassificationConstants { - /** - * Whether the smart linkify feature is enabled. - */ + /** Whether the smart linkify feature is enabled. */ private static final String SMART_LINKIFY_ENABLED = "smart_linkify_enabled"; - /** - * Whether SystemTextClassifier is enabled. - */ + + /** Whether SystemTextClassifier is enabled. */ static final String SYSTEM_TEXT_CLASSIFIER_ENABLED = "system_textclassifier_enabled"; - /** - * Whether TextClassifierImpl is enabled. - */ + + /** Whether TextClassifierImpl is enabled. */ @VisibleForTesting static final String LOCAL_TEXT_CLASSIFIER_ENABLED = "local_textclassifier_enabled"; - /** - * Enable smart selection without a visible UI changes. - */ + + /** Enable smart selection without a visible UI changes. */ private static final String MODEL_DARK_LAUNCH_ENABLED = "model_dark_launch_enabled"; - /** - * Whether the smart selection feature is enabled. - */ + + /** Whether the smart selection feature is enabled. */ private static final String SMART_SELECTION_ENABLED = "smart_selection_enabled"; - /** - * Whether the smart text share feature is enabled. - */ + + /** Whether the smart text share feature is enabled. */ private static final String SMART_TEXT_SHARE_ENABLED = "smart_text_share_enabled"; - /** - * Whether animation for smart selection is enabled. - */ - private static final String SMART_SELECT_ANIMATION_ENABLED = - "smart_select_animation_enabled"; - /** - * Max length of text that generateLinks can accept. - */ + + /** Whether animation for smart selection is enabled. */ + private static final String SMART_SELECT_ANIMATION_ENABLED = "smart_select_animation_enabled"; + + /** Max length of text that generateLinks can accept. */ @VisibleForTesting static final String GENERATE_LINKS_MAX_TEXT_LENGTH = "generate_links_max_text_length"; + /** * The TextClassifierService which would like to use. Example of setting the package: + * * <pre> * adb shell cmd device_config put textclassifier textclassifier_service_package_override \ * com.android.textclassifier @@ -83,8 +77,8 @@ public final class TextClassificationConstants { "textclassifier_service_package_override"; /** - * The timeout value in seconds used by {@link SystemTextClassifier} for each TextClassifier - * API calls. + * The timeout value in seconds used by {@link SystemTextClassifier} for each TextClassifier API + * calls. */ @VisibleForTesting static final String SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND = @@ -109,64 +103,144 @@ public final class TextClassificationConstants { private static final long SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT = 60; private static final int SMART_SELECTION_TRIM_DELTA_DEFAULT = 120; + private static final Object sLock = new Object(); + private static volatile boolean sMemoizedValuesInitialized; + private static boolean sLocalTextClassifierEnabled; + private static boolean sSystemTextClassifierEnabled; + private static boolean sModelDarkLaunchEnabled; + private static boolean sSmartSelectionEnabled; + private static boolean sSmartTextShareEnabled; + private static boolean sSmartLinkifyEnabled; + private static boolean sSmartSelectAnimationEnabled; + private static int sGenerateLinksMaxTextLength; + private static long sSystemTextClassifierApiTimeoutInSecond; + private static int sSmartSelectionTrimDelta; + + /** + * For DeviceConfig values where we don't care if they change at runtime, fetch them once and + * memoize their values. + */ + private static void ensureMemoizedValues() { + if (sMemoizedValuesInitialized) { + return; + } + synchronized (sLock) { + if (sMemoizedValuesInitialized) { + return; + } + + // Read all namespace properties so we get a single snapshot (values + // fetched aren't updated in the interim). + DeviceConfig.Properties properties = + DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TEXTCLASSIFIER); + sLocalTextClassifierEnabled = + properties.getBoolean( + LOCAL_TEXT_CLASSIFIER_ENABLED, + LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT); + sSystemTextClassifierEnabled = + properties.getBoolean( + SYSTEM_TEXT_CLASSIFIER_ENABLED, + SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT); + sModelDarkLaunchEnabled = + properties.getBoolean( + MODEL_DARK_LAUNCH_ENABLED, + MODEL_DARK_LAUNCH_ENABLED_DEFAULT); + sSmartSelectionEnabled = + properties.getBoolean( + SMART_SELECTION_ENABLED, + SMART_SELECTION_ENABLED_DEFAULT); + sSmartTextShareEnabled = + properties.getBoolean( + SMART_TEXT_SHARE_ENABLED, + SMART_TEXT_SHARE_ENABLED_DEFAULT); + sSmartLinkifyEnabled = + properties.getBoolean( + SMART_LINKIFY_ENABLED, + SMART_LINKIFY_ENABLED_DEFAULT); + sSmartSelectAnimationEnabled = + properties.getBoolean( + SMART_SELECT_ANIMATION_ENABLED, + SMART_SELECT_ANIMATION_ENABLED_DEFAULT); + sGenerateLinksMaxTextLength = + properties.getInt( + GENERATE_LINKS_MAX_TEXT_LENGTH, + GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT); + sSystemTextClassifierApiTimeoutInSecond = + properties.getLong( + SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND, + SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT); + sSmartSelectionTrimDelta = + properties.getInt( + SMART_SELECTION_TRIM_DELTA, + SMART_SELECTION_TRIM_DELTA_DEFAULT); + + sMemoizedValuesInitialized = true; + } + } + + @VisibleForTesting + public static void resetMemoizedValues() { + sMemoizedValuesInitialized = false; + } + @Nullable public String getTextClassifierServicePackageOverride() { - return DeviceConfig.getString(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, + // Don't memoize this value because we want to be able to receive config + // updates at runtime. + return DeviceConfig.getString( + DeviceConfig.NAMESPACE_TEXTCLASSIFIER, TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE, DEFAULT_TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE); } public boolean isLocalTextClassifierEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - LOCAL_TEXT_CLASSIFIER_ENABLED, LOCAL_TEXT_CLASSIFIER_ENABLED_DEFAULT); + ensureMemoizedValues(); + return sLocalTextClassifierEnabled; } public boolean isSystemTextClassifierEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - SYSTEM_TEXT_CLASSIFIER_ENABLED, - SYSTEM_TEXT_CLASSIFIER_ENABLED_DEFAULT); + ensureMemoizedValues(); + return sSystemTextClassifierEnabled; } public boolean isModelDarkLaunchEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - MODEL_DARK_LAUNCH_ENABLED, MODEL_DARK_LAUNCH_ENABLED_DEFAULT); + ensureMemoizedValues(); + return sModelDarkLaunchEnabled; } public boolean isSmartSelectionEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - SMART_SELECTION_ENABLED, SMART_SELECTION_ENABLED_DEFAULT); + ensureMemoizedValues(); + return sSmartSelectionEnabled; } public boolean isSmartTextShareEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - SMART_TEXT_SHARE_ENABLED, SMART_TEXT_SHARE_ENABLED_DEFAULT); + ensureMemoizedValues(); + return sSmartTextShareEnabled; } public boolean isSmartLinkifyEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, SMART_LINKIFY_ENABLED, - SMART_LINKIFY_ENABLED_DEFAULT); + ensureMemoizedValues(); + return sSmartLinkifyEnabled; } public boolean isSmartSelectionAnimationEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - SMART_SELECT_ANIMATION_ENABLED, SMART_SELECT_ANIMATION_ENABLED_DEFAULT); + ensureMemoizedValues(); + return sSmartSelectAnimationEnabled; } public int getGenerateLinksMaxTextLength() { - return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - GENERATE_LINKS_MAX_TEXT_LENGTH, GENERATE_LINKS_MAX_TEXT_LENGTH_DEFAULT); + ensureMemoizedValues(); + return sGenerateLinksMaxTextLength; } public long getSystemTextClassifierApiTimeoutInSecond() { - return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND, - SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND_DEFAULT); + ensureMemoizedValues(); + return sSystemTextClassifierApiTimeoutInSecond; } public int getSmartSelectionTrimDelta() { - return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TEXTCLASSIFIER, - SMART_SELECTION_TRIM_DELTA, - SMART_SELECTION_TRIM_DELTA_DEFAULT); + ensureMemoizedValues(); + return sSmartSelectionTrimDelta; } void dump(IndentingPrintWriter pw) { @@ -180,11 +254,15 @@ public final class TextClassificationConstants { pw.print(SMART_SELECTION_ENABLED, isSmartSelectionEnabled()).println(); pw.print(SMART_TEXT_SHARE_ENABLED, isSmartTextShareEnabled()).println(); pw.print(SYSTEM_TEXT_CLASSIFIER_ENABLED, isSystemTextClassifierEnabled()).println(); - pw.print(TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE, - getTextClassifierServicePackageOverride()).println(); - pw.print(SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND, - getSystemTextClassifierApiTimeoutInSecond()).println(); + pw.print( + TEXT_CLASSIFIER_SERVICE_PACKAGE_OVERRIDE, + getTextClassifierServicePackageOverride()) + .println(); + pw.print( + SYSTEM_TEXT_CLASSIFIER_API_TIMEOUT_IN_SECOND, + getSystemTextClassifierApiTimeoutInSecond()) + .println(); pw.print(SMART_SELECTION_TRIM_DELTA, getSmartSelectionTrimDelta()).println(); pw.decreaseIndent(); } -}
\ No newline at end of file +} diff --git a/core/java/android/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java index 0601b2a79268..bed0e0e8a225 100644 --- a/core/java/android/window/InputTransferToken.java +++ b/core/java/android/window/InputTransferToken.java @@ -16,6 +16,7 @@ package android.window; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.os.Binder; import android.os.IBinder; @@ -23,13 +24,15 @@ import android.os.Parcel; import android.os.Parcelable; import android.view.SurfaceControlViewHost; +import com.android.window.flags.Flags; + import java.util.Objects; /** * A token that can be used to request focus on or to transfer touch gesture to a * {@link SurfaceControlViewHost} or {@link android.view.SurfaceControl} that has an input channel. - * @hide */ +@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER) public final class InputTransferToken implements Parcelable { /** * @hide diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java index 90f8834b37d1..a30c8fa2c63c 100644 --- a/core/java/android/window/TrustedPresentationThresholds.java +++ b/core/java/android/window/TrustedPresentationThresholds.java @@ -19,15 +19,15 @@ package android.window; import android.annotation.FlaggedApi; import android.annotation.FloatRange; import android.annotation.IntRange; -import android.annotation.SuppressLint; import android.os.Parcel; import android.os.Parcelable; -import android.view.SurfaceControl; import androidx.annotation.NonNull; import com.android.window.flags.Flags; +import java.util.Objects; + /** * Threshold values that are sent with * {@link android.view.WindowManager#registerTrustedPresentationListener(IBinder, @@ -36,33 +36,53 @@ import com.android.window.flags.Flags; @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) public final class TrustedPresentationThresholds implements Parcelable { /** - * The min alpha the {@link SurfaceControl} is required to have to be considered inside the + * The min alpha the Window is required to have to be considered inside the * threshold. */ @FloatRange(from = 0f, fromInclusive = false, to = 1f) - @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) - @SuppressLint("InternalField") // simple data class - public final float minAlpha; + private final float mMinAlpha; /** - * The min fraction of the SurfaceControl that was presented to the user to be considered + * The min fraction of the Window that was presented to the user to be considered * inside the threshold. */ @FloatRange(from = 0f, fromInclusive = false, to = 1f) - @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) - @SuppressLint("InternalField") // simple data class - public final float minFractionRendered; + private final float mMinFractionRendered; /** - * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold. + * The time in milliseconds required for the Window to be in the threshold. */ @IntRange(from = 1) + private final int mStabilityRequirementMs; + + /** + * The min alpha the Window is required to have to be considered inside the + * threshold. + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public @FloatRange(from = 0f, fromInclusive = false, to = 1f) float getMinAlpha() { + return mMinAlpha; + } + + /** + * The min fraction of the Window that was presented to the user to be considered + * inside the threshold. + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public @FloatRange(from = 0f, fromInclusive = false, to = 1f) float getMinFractionRendered() { + return mMinFractionRendered; + } + + /** + * The time in milliseconds required for the Window to be in the threshold. + */ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) - @SuppressLint("InternalField") // simple data class - public final int stabilityRequirementMs; + public @IntRange(from = 1) int getStabilityRequirementMillis() { + return mStabilityRequirementMs; + } private void checkValid() { - if (minAlpha <= 0 || minFractionRendered <= 0 || stabilityRequirementMs < 1) { + if (mMinAlpha <= 0 || mMinFractionRendered <= 0 || mStabilityRequirementMs < 1) { throw new IllegalArgumentException( "TrustedPresentationThresholds values are invalid"); } @@ -71,23 +91,23 @@ public final class TrustedPresentationThresholds implements Parcelable { /** * Creates a new TrustedPresentationThresholds. * - * @param minAlpha The min alpha the {@link SurfaceControl} is required to + * @param minAlpha The min alpha the Window is required to * have to be considered inside the * threshold. - * @param minFractionRendered The min fraction of the SurfaceControl that was presented + * @param minFractionRendered The min fraction of the Window that was presented * to the user to be considered * inside the threshold. * @param stabilityRequirementMs The time in milliseconds required for the - * {@link SurfaceControl} to be in the threshold. + * Window to be in the threshold. */ @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) public TrustedPresentationThresholds( @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha, @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered, @IntRange(from = 1) int stabilityRequirementMs) { - this.minAlpha = minAlpha; - this.minFractionRendered = minFractionRendered; - this.stabilityRequirementMs = stabilityRequirementMs; + this.mMinAlpha = minAlpha; + this.mMinFractionRendered = minFractionRendered; + this.mStabilityRequirementMs = stabilityRequirementMs; checkValid(); } @@ -95,18 +115,18 @@ public final class TrustedPresentationThresholds implements Parcelable { @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) public String toString() { return "TrustedPresentationThresholds { " - + "minAlpha = " + minAlpha + ", " - + "minFractionRendered = " + minFractionRendered + ", " - + "stabilityRequirementMs = " + stabilityRequirementMs + + "minAlpha = " + mMinAlpha + ", " + + "minFractionRendered = " + mMinFractionRendered + ", " + + "stabilityRequirementMs = " + mStabilityRequirementMs + " }"; } @Override @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeFloat(minAlpha); - dest.writeFloat(minFractionRendered); - dest.writeInt(stabilityRequirementMs); + dest.writeFloat(mMinAlpha); + dest.writeFloat(mMinFractionRendered); + dest.writeInt(mStabilityRequirementMs); } @Override @@ -115,13 +135,34 @@ public final class TrustedPresentationThresholds implements Parcelable { return 0; } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public int hashCode() { + return Objects.hash(mMinAlpha, mMinFractionRendered, mStabilityRequirementMs); + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TrustedPresentationThresholds that)) { + return false; + } + return mMinAlpha == that.mMinAlpha + && mMinFractionRendered == that.mMinFractionRendered + && mStabilityRequirementMs == that.mStabilityRequirementMs; + } + /** * @hide */ TrustedPresentationThresholds(@NonNull Parcel in) { - minAlpha = in.readFloat(); - minFractionRendered = in.readFloat(); - stabilityRequirementMs = in.readInt(); + mMinAlpha = in.readFloat(); + mMinFractionRendered = in.readFloat(); + mStabilityRequirementMs = in.readInt(); checkValid(); } diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 069affb4c06c..3ffa27451557 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -20,14 +20,6 @@ flag { flag { namespace: "window_surfaces" - name: "get_host_token_api" - description: "Feature flag to associate the host and embedded windows" - is_fixed_read_only: true - bug: "304508760" -} - -flag { - namespace: "window_surfaces" name: "transfer_gesture_to_embedded" description: "Enable public API for Window Surfaces" bug: "287076178" diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl index ba87caa0697c..5cb5963112a6 100644 --- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl +++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl @@ -40,6 +40,13 @@ oneway interface IHotwordRecognitionStatusCallback { in SoundTrigger.KeyphraseRecognitionEvent recognitionEvent, in HotwordDetectedResult result); + /** + * Called when the keyphrase is detected from audio coming from an external source. + * + * @param result Successful detection result payload. + */ + void onKeyphraseDetectedFromExternalSource(in HotwordDetectedResult result); + /** * Called when a generic sound trigger event is witnessed. * diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 31ccf6d6dc1d..314ed69cb885 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -382,14 +382,4 @@ interface IVoiceInteractionManagerService { oneway void notifyActivityEventChanged( in IBinder activityToken, int type); - - /** - * Allows/disallows receiving training data from trusted process. - * Caller must be the active assistant and a preinstalled assistant. - * - * @param allowed whether to allow/disallow receiving training data produced during - * sandboxed detection (from trusted process). - */ - @EnforcePermission("MANAGE_HOTWORD_DETECTION") - void setShouldReceiveSandboxedTrainingData(boolean allowed); } diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index ed943cb2385d..eef6ce7619e8 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -57,7 +57,7 @@ import java.util.concurrent.atomic.AtomicReference; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host") + "com.android.platform.test.ravenwood.nativesubstitution.LongArrayMultiStateCounter_host") public final class LongArrayMultiStateCounter implements Parcelable { /** @@ -65,7 +65,7 @@ public final class LongArrayMultiStateCounter implements Parcelable { */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution" + "com.android.platform.test.ravenwood.nativesubstitution" + ".LongArrayMultiStateCounter_host$LongArrayContainer_host") public static class LongArrayContainer { private static NativeAllocationRegistry sRegistry; diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java index 064609f9dfe4..e5662c7d5145 100644 --- a/core/java/com/android/internal/os/LongMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongMultiStateCounter.java @@ -57,7 +57,7 @@ import libcore.util.NativeAllocationRegistry; */ @android.ravenwood.annotation.RavenwoodKeepWholeClass @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( - "com.android.hoststubgen.nativesubstitution.LongMultiStateCounter_host") + "com.android.platform.test.ravenwood.nativesubstitution.LongMultiStateCounter_host") public final class LongMultiStateCounter implements Parcelable { private static NativeAllocationRegistry sRegistry; diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto index 8c3304137904..d9ed911515ba 100644 --- a/core/proto/android/app/appstartinfo.proto +++ b/core/proto/android/app/appstartinfo.proto @@ -39,4 +39,5 @@ message ApplicationStartInfoProto { optional AppStartStartType start_type = 9; optional bytes start_intent = 10; optional AppStartLaunchMode launch_mode = 11; + optional bool was_force_stopped = 12; } diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto index 9359528b613b..e368c6a98a75 100644 --- a/core/proto/android/hardware/sensorprivacy.proto +++ b/core/proto/android/hardware/sensorprivacy.proto @@ -91,6 +91,9 @@ message SensorPrivacyIndividualEnabledSensorProto { enum StateType { ENABLED = 1; DISABLED = 2; + AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3; + AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4; + AUTO_DRIVER_ASSISTANCE_APPS = 5; } // DEPRECATED @@ -134,4 +137,4 @@ message SensorPrivacyToggleSourceProto { // Source for which sensor privacy was toggled. optional Source source = 1; -}
\ No newline at end of file +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index e4e8f7ef5247..e7df19ce55ca 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -836,6 +836,7 @@ <protected-broadcast android:name="android.intent.action.PROFILE_AVAILABLE" /> <protected-broadcast android:name="android.intent.action.PROFILE_UNAVAILABLE" /> <protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" /> + <protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -1733,6 +1734,16 @@ android:protectionLevel="signature" android:featureFlag="com.android.internal.camera.flags.camera_hsum_permission" /> + + <!-- @SystemApi Allows camera access of allowlisted driver assistance apps + to be controlled separately. + <p> Not for use by third-party applications. + @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") + @hide + --> + <permission android:name="android.permission.CAMERA_PRIVACY_ALLOWLIST" + android:protectionLevel="signature|privileged" /> + <!-- ====================================================================== --> <!-- Permissions for accessing the device sensors --> <!-- ====================================================================== --> @@ -6873,10 +6884,13 @@ <permission android:name="android.permission.ACCESS_DRM_CERTIFICATES" android:protectionLevel="signature|privileged" /> - <!-- Api Allows an application to manage media projection sessions. + <!-- Allows an application to manage media projection sessions, by showing the permission dialog + to the user and creating a new token for each capture session. + @FlaggedApi("com.android.media.flags.limit_manage_media_projection") + @SystemApi Only granted to apps holding role SYSTEM_UI. @hide This is not a third-party API (intended for system apps). --> <permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" - android:protectionLevel="signature" /> + android:protectionLevel="internal|role" /> <!-- @SystemApi Allows an application to read install sessions @hide This is not a third-party API (intended for system apps). --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 647bccfb0c89..734ff8db4944 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -3289,7 +3289,8 @@ {@link java.lang.SecurityException}. <p> Note that the enforcement works for content URIs inside - {@link android.content.Intent#getData} and {@link android.content.Intent#getClipData}. + {@link android.content.Intent#getData}, {@link android.content.Intent#EXTRA_STREAM}, + and {@link android.content.Intent#getClipData}. @FlaggedApi("android.security.content_uri_permission_apis") --> <attr name="requireContentUriPermissionFromCaller" format="string"> <!-- Default, no specific permissions are required. --> diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp index 6be553b99c3c..beffb9aac12b 100644 --- a/core/tests/BroadcastRadioTests/Android.bp +++ b/core/tests/BroadcastRadioTests/Android.bp @@ -18,6 +18,7 @@ package { // all of the 'license_kinds' from "frameworks_base_license" // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 + default_team: "trendy_team_aaos_framework", default_applicable_licenses: ["frameworks_base_license"], } diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java index 661b210f3e18..402d08eb4a25 100644 --- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java +++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java @@ -27,6 +27,8 @@ import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyResourcesManager; @@ -118,12 +120,55 @@ public class CrossProfileAppsTest { public void initUsers() throws Exception { when(mUserManager.isManagedProfile(PERSONAL_PROFILE.getIdentifier())).thenReturn(false); when(mUserManager.isManagedProfile(MANAGED_PROFILE.getIdentifier())).thenReturn(true); + when(mUserManager.isProfile(PERSONAL_PROFILE.getIdentifier())).thenReturn(false); + when(mUserManager.isProfile(MANAGED_PROFILE.getIdentifier())).thenReturn(true); mTargetProfiles = new ArrayList<>(); when(mService.getTargetUserProfiles(MY_PACKAGE)).thenReturn(mTargetProfiles); } @Test + public void isProfile_managedProfile_returnsTrue() { + setValidTargetProfile(MANAGED_PROFILE); + + boolean result = mCrossProfileApps.isProfile(MANAGED_PROFILE); + + assertTrue(result); + } + + @Test + public void isProfile_personalProfile_returnsFalse() { + setValidTargetProfile(PERSONAL_PROFILE); + + boolean result = mCrossProfileApps.isProfile(PERSONAL_PROFILE); + + assertFalse(result); + } + + @Test + public void isManagedProfile_managedProfile_returnsTrue() { + setValidTargetProfile(MANAGED_PROFILE); + + boolean result = mCrossProfileApps.isManagedProfile(MANAGED_PROFILE); + + assertTrue(result); + } + + @Test + public void isManagedProfile_personalProfile_returnsFalse() { + setValidTargetProfile(PERSONAL_PROFILE); + + boolean result = mCrossProfileApps.isManagedProfile(PERSONAL_PROFILE); + + assertFalse(result); + } + + @Test(expected = SecurityException.class) + public void isManagedProfile_notValidTarget_throwsSecurityException() { + mCrossProfileApps.isManagedProfile(PERSONAL_PROFILE); + } + + @Test public void getProfileSwitchingLabel_managedProfile() { setValidTargetProfile(MANAGED_PROFILE); when(mApplicationInfo.loadSafeLabel(any(), anyFloat(), anyInt())).thenReturn("app"); diff --git a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java index aa89e04b4b7e..cc342cf0fc80 100644 --- a/core/tests/coretests/src/android/os/RemoteCallbackListTest.java +++ b/core/tests/coretests/src/android/os/RemoteCallbackListTest.java @@ -17,6 +17,7 @@ package android.os; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import androidx.test.runner.AndroidJUnit4; @@ -24,6 +25,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; @@ -59,10 +62,17 @@ public class RemoteCallbackListTest { mList.register(mRed.mInterface); mList.register(mGreen.mInterface, mCookie); assertEquals(2, mList.getRegisteredCallbackCount()); - assertEquals(mRed.mInterface, mList.getRegisteredCallbackItem(0)); - assertEquals(null, mList.getRegisteredCallbackCookie(0)); - assertEquals(mGreen.mInterface, mList.getRegisteredCallbackItem(1)); - assertEquals(mCookie, mList.getRegisteredCallbackCookie(1)); + + final List<IRemoteCallback> list = new ArrayList<>(); + for (int i = 0; i < mList.getRegisteredCallbackCount(); i++) { + list.add(mList.getRegisteredCallbackItem(i)); + } + final int redIndex = list.indexOf(mRed.mInterface); + final int greenIndex = list.indexOf(mGreen.mInterface); + assertTrue(redIndex >= 0); + assertTrue(greenIndex >= 0); + assertEquals(null, mList.getRegisteredCallbackCookie(redIndex)); + assertEquals(mCookie, mList.getRegisteredCallbackCookie(greenIndex)); mList.unregister(mRed.mInterface); assertEquals(1, mList.getRegisteredCallbackCount()); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java index 16e750a3c12d..a567b4bd5a48 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationConstantsTest.java @@ -23,6 +23,7 @@ import android.provider.DeviceConfig; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +33,11 @@ import java.util.function.Consumer; @RunWith(AndroidJUnit4.class) public class TextClassificationConstantsTest { + @Before + public void setup() { + TextClassificationConstants.resetMemoizedValues(); + } + @Test public void booleanSettings() { assertSettings( diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 4982f3732089..244fe3033dca 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -618,7 +618,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -633,7 +633,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #getMgf1Digests() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -1292,7 +1292,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 7b6b2d142f95..2495d1a85864 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -401,7 +401,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -416,7 +416,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #getMgf1Digests() */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -799,7 +799,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER_V2) public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index 83ddfc5cf1a1..e6c652c14c71 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -974,7 +974,7 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato private static boolean getMgf1DigestSetterFlag() { try { - return Flags.mgf1DigestSetter(); + return Flags.mgf1DigestSetterV2(); } catch (SecurityException e) { Log.w(TAG, "Cannot read MGF1 Digest setter flag value", e); return false; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 2d8c5a380c6b..e6a63b9c4c17 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -259,7 +259,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { private static boolean getMgf1DigestSetterFlag() { try { - return Flags.mgf1DigestSetter(); + return Flags.mgf1DigestSetterV2(); } catch (SecurityException e) { Log.w(NAME, "Cannot read MGF1 Digest setter flag value", e); return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt index fd91ac0affc5..e1e41ee1e64d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt @@ -58,7 +58,7 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl return false } - return controller.moveToDesktopWithoutDecor(taskId, WindowContainerTransaction()) + return controller.moveToDesktop(taskId, WindowContainerTransaction()) } override fun printShellCommandHelp(pw: PrintWriter, prefix: String) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 42c8d7417611..837cb99602c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -244,47 +244,14 @@ class DesktopTasksController( fun moveToDesktop( taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction() - ) { + ): Boolean { shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task, wct) - } - } - - /** Move a task with given `taskId` to desktop without decor */ - fun moveToDesktopWithoutDecor( - taskId: Int, - wct: WindowContainerTransaction - ): Boolean { - val task = shellTaskOrganizer.getRunningTaskInfo(taskId) ?: return false - moveToDesktopWithoutDecor(task, wct) + } ?: return false return true } /** - * Move a task to desktop without decor - */ - private fun moveToDesktopWithoutDecor( - task: RunningTaskInfo, - wct: WindowContainerTransaction - ) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: moveToDesktopWithoutDecor taskId=%d", - task.taskId - ) - exitSplitIfApplicable(wct, task) - // Bring other apps to front first - bringDesktopAppsToFront(task.displayId, wct) - addMoveToDesktopChanges(wct, task) - - if (Transitions.ENABLE_SHELL_TRANSITIONS) { - transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) - } else { - shellTaskOrganizer.applyTransaction(wct) - } - } - - /** * Move a task to desktop */ fun moveToDesktop( diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index e9f4b81c7624..80b6c0385fca 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -18,7 +18,6 @@ #include <SkFontMetrics.h> #include <SkRRect.h> -#include <minikin/MinikinRect.h> #include "FeatureFlags.h" #include "MinikinUtils.h" @@ -108,13 +107,7 @@ void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, // care of all alignment. paint.setTextAlign(Paint::kLeft_Align); - minikin::MinikinRect bounds; - // We only need the bounds to draw a rectangular background in high contrast mode. Let's save - // the cycles otherwise. - if (flags::high_contrast_text_small_text_rect() && isHighContrastText()) { - MinikinUtils::getBounds(&paint, bidiFlags, typeface, text, textSize, &bounds); - } - DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance(), bounds); + DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance()); MinikinUtils::forFontRun(layout, &paint, f); if (text_feature::fix_double_underline()) { diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index ba6543988a7b..02bf0d8d5e95 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -16,6 +16,7 @@ #include <SkFontMetrics.h> #include <SkRRect.h> +#include <SkTextBlob.h> #include <com_android_graphics_hwui_flags.h> #include "../utils/Color.h" @@ -66,7 +67,7 @@ public: * @param bounds bounds of the text. Only required if high contrast text mode is enabled. */ DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x, - float y, float totalAdvance, const minikin::MinikinRect& bounds) + float y, float totalAdvance) : layout(layout) , canvas(canvas) , paint(paint) @@ -74,8 +75,7 @@ public: , y(y) , totalAdvance(totalAdvance) , underlinePosition(0) - , underlineThickness(0) - , bounds(bounds) {} + , underlineThickness(0) {} void operator()(size_t start, size_t end) { auto glyphFunc = [&](uint16_t* text, float* positions) { @@ -106,12 +106,32 @@ public: simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); if (flags::high_contrast_text_small_text_rect()) { - auto bgBounds(bounds); - auto padding = kHighContrastTextBorderWidth + 0.1f * paint.getSkFont().getSize(); - bgBounds.offset(x, y); - canvas->drawRect(bgBounds.mLeft - padding, bgBounds.mTop - padding, - bgBounds.mRight + padding, bgBounds.mBottom + padding, - outlinePaint); + const SkFont& font = paint.getSkFont(); + auto padding = kHighContrastTextBorderWidth + 0.1f * font.getSize(); + + // Draw the background only behind each glyph's bounds. We do this instead of using + // the bounds of the entire layout, because the layout includes alignment whitespace + // etc which can obscure other text from separate passes (e.g. emojis). + // Merge all the glyph bounds into one rect for this line, since drawing a rect for + // each glyph is expensive. + SkRect glyphBounds; + SkRect bgBounds; + for (size_t i = start; i < end; i++) { + auto glyph = layout.getGlyphId(i); + + font.getBounds(reinterpret_cast<const SkGlyphID*>(&glyph), 1, &glyphBounds, + &paint); + glyphBounds.offset(layout.getX(i), layout.getY(i)); + + bgBounds.join(glyphBounds); + } + + if (!bgBounds.isEmpty()) { + bgBounds.offset(x, y); + bgBounds.outset(padding, padding); + canvas->drawRect(bgBounds.fLeft, bgBounds.fTop, bgBounds.fRight, + bgBounds.fBottom, outlinePaint); + } } else { canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance); } @@ -169,7 +189,6 @@ private: float totalAdvance; float underlinePosition; float underlineThickness; - const minikin::MinikinRect& bounds; }; } // namespace android diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp index 9911bfa70443..c70a30477ecf 100644 --- a/libs/hwui/tests/unit/UnderlineTest.cpp +++ b/libs/hwui/tests/unit/UnderlineTest.cpp @@ -103,9 +103,8 @@ DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) // Create minikin::Layout std::unique_ptr<Typeface> typeface(makeTypeface()); minikin::Layout layout = doLayout(text, *paint, typeface.get()); - minikin::MinikinRect bounds; - DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance(), bounds); + DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance()); MinikinUtils::forFontRun(layout, paint, f); return f; } diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig index a96fe47f2381..0fa641b926a9 100644 --- a/location/java/android/location/flags/location.aconfig +++ b/location/java/android/location/flags/location.aconfig @@ -1,6 +1,13 @@ package: "android.location.flags" flag { + name: "location_bypass" + namespace: "location" + description: "Enable location bypass appops behavior" + bug: "301150056" +} + +flag { name: "fix_service_watcher" namespace: "location" description: "Enable null explicit services in ServiceWatcher" diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 4ee25c4bcf60..ac94a6f6ad3c 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -16,6 +16,7 @@ package android.media; +import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME; @@ -2213,6 +2214,18 @@ final public class MediaCodec { */ public static final int CONFIGURE_FLAG_USE_CRYPTO_ASYNC = 4; + /** + * Configure the codec with a detached output surface. + * <p> + * This flag is only defined for a video decoder. MediaCodec + * configured with this flag will be in Surface mode even though + * the surface parameter is null. + * + * @see detachOutputSurface + */ + @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE) + public static final int CONFIGURE_FLAG_DETACHED_SURFACE = 8; + /** @hide */ @IntDef( flag = true, @@ -2395,6 +2408,31 @@ final public class MediaCodec { private native void native_setSurface(@NonNull Surface surface); /** + * Detach the current output surface of a codec. + * <p> + * Detaches the currently associated output Surface from the + * MediaCodec decoder. This allows the SurfaceView or other + * component holding the Surface to be safely destroyed or + * modified without affecting the decoder's operation. After + * calling this method (and after it returns), the decoder will + * enter detached-Surface mode and will no longer render + * output. + * + * @throws IllegalStateException if the codec was not + * configured in surface mode. + * @see CONFIGURE_FLAG_DETACHED_SURFACE + */ + @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE) + public void detachOutputSurface() { + if (!mHasSurface) { + throw new IllegalStateException("codec was not configured for an output surface"); + } + // note: we still have a surface in detached mode, so keep mHasSurface + // we also technically allow calling detachOutputSurface multiple times in a row + // native_detachSurface(); + } + + /** * Create a persistent input surface that can be used with codecs that normally have an input * surface, such as video encoders. A persistent input can be reused by subsequent * {@link MediaCodec} or {@link MediaRecorder} instances, but can only be used by at @@ -3212,6 +3250,51 @@ final public class MediaCodec { } } + /** + * Similar to {@link #queueInputBuffers queueInputBuffers} but submits multiple access units + * in a buffer that is potentially encrypted. + * <strong>Check out further notes at {@link #queueInputBuffers queueInputBuffers}.</strong> + * + * @param index The index of a client-owned input buffer previously returned + * in a call to {@link #dequeueInputBuffer}. + * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the + * contents in the buffer. The ArrayDeque and the BufferInfo objects provided + * can be recycled by the caller for re-use. + * @param cryptoInfos ArrayDeque of {@link MediaCodec.CryptoInfo} objects to facilitate the + * decryption of the contents. The ArrayDeque and the CryptoInfo objects + * provided can be reused immediately after the call returns. These objects + * should correspond to bufferInfo objects to ensure correct decryption. + * @throws IllegalStateException if not in the Executing state or not in asynchronous mode. + * @throws MediaCodec.CodecException upon codec error. + * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the + * access units are not contiguous. + * @throws CryptoException if an error occurs while attempting to decrypt the buffer. + * An error code associated with the exception helps identify the + * reason for the failure. + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public final void queueSecureInputBuffers( + int index, + @NonNull ArrayDeque<BufferInfo> bufferInfos, + @NonNull ArrayDeque<CryptoInfo> cryptoInfos) { + synchronized(mBufferLock) { + if (mBufferMode == BUFFER_MODE_BLOCK) { + throw new IncompatibleWithBlockModelException("queueSecureInputBuffers() " + + "is not compatible with CONFIGURE_FLAG_USE_BLOCK_MODEL. " + + "Please use getQueueRequest() to queue buffers"); + } + invalidateByteBufferLocked(mCachedInputBuffers, index, true /* input */); + mDequeuedInputBuffers.remove(index); + } + try { + native_queueSecureInputBuffers( + index, bufferInfos.toArray(), cryptoInfos.toArray()); + } catch (CryptoException | IllegalStateException | IllegalArgumentException e) { + revalidateByteBuffer(mCachedInputBuffers, index, true /* input */); + throw e; + } + } + private native final void native_queueSecureInputBuffer( int index, int offset, @@ -3219,6 +3302,11 @@ final public class MediaCodec { long presentationTimeUs, int flags) throws CryptoException; + private native final void native_queueSecureInputBuffers( + int index, + @NonNull Object[] bufferInfos, + @NonNull Object[] cryptoInfos) throws CryptoException, CodecException; + /** * Returns the index of an input buffer to be filled with valid data * or -1 if no such buffer is currently available. @@ -3464,7 +3552,7 @@ final public class MediaCodec { mLinearBlock = block; mOffset = offset; mSize = size; - mCryptoInfo = null; + mCryptoInfos.clear(); return this; } @@ -3498,7 +3586,44 @@ final public class MediaCodec { mLinearBlock = block; mOffset = offset; mSize = size; - mCryptoInfo = cryptoInfo; + mCryptoInfos.clear(); + mCryptoInfos.add(cryptoInfo); + return this; + } + + /** + * Set an encrypted linear block to this queue request. Exactly one buffer must be + * set for a queue request before calling {@link #queue}. The block can contain multiple + * access units and if present should be laid out contiguously and without gaps. + * + * @param block The linear block object + * @param bufferInfos ArrayDeque of {@link MediaCodec.BufferInfo} that describes the + * contents in the buffer. The ArrayDeque and the BufferInfo objects + * provided can be recycled by the caller for re-use. + * @param cryptoInfos ArrayDeque of {@link MediaCodec.CryptoInfo} that describes the + * structure of the encrypted input samples. The ArrayDeque and the + * BufferInfo objects provided can be recycled by the caller for re-use. + * @return this object + * @throws IllegalStateException if a buffer is already set + * @throws IllegalArgumentException upon if bufferInfos is empty, contains null, or if the + * access units are not contiguous. + */ + @FlaggedApi(FLAG_LARGE_AUDIO_FRAME) + public @NonNull QueueRequest setMultiFrameEncryptedLinearBlock( + @NonNull LinearBlock block, + @NonNull ArrayDeque<MediaCodec.BufferInfo> bufferInfos, + @NonNull ArrayDeque<MediaCodec.CryptoInfo> cryptoInfos) { + if (!isAccessible()) { + throw new IllegalStateException("The request is stale"); + } + if (mLinearBlock != null || mHardwareBuffer != null) { + throw new IllegalStateException("Cannot set block twice"); + } + mLinearBlock = block; + mBufferInfos.clear(); + mBufferInfos.addAll(bufferInfos); + mCryptoInfos.clear(); + mCryptoInfos.addAll(cryptoInfos); return this; } @@ -3710,8 +3835,10 @@ final public class MediaCodec { mBufferInfos.add(info); } if (mLinearBlock != null) { + mCodec.native_queueLinearBlock( - mIndex, mLinearBlock, mCryptoInfo, + mIndex, mLinearBlock, + mCryptoInfos.isEmpty() ? null : mCryptoInfos.toArray(), mBufferInfos.toArray(), mTuningKeys, mTuningValues); } else if (mHardwareBuffer != null) { @@ -3726,11 +3853,11 @@ final public class MediaCodec { mLinearBlock = null; mOffset = 0; mSize = 0; - mCryptoInfo = null; mHardwareBuffer = null; mPresentationTimeUs = 0; mFlags = 0; mBufferInfos.clear(); + mCryptoInfos.clear(); mTuningKeys.clear(); mTuningValues.clear(); return this; @@ -3750,11 +3877,11 @@ final public class MediaCodec { private LinearBlock mLinearBlock = null; private int mOffset = 0; private int mSize = 0; - private MediaCodec.CryptoInfo mCryptoInfo = null; private HardwareBuffer mHardwareBuffer = null; private long mPresentationTimeUs = 0; private @BufferFlag int mFlags = 0; private final ArrayDeque<BufferInfo> mBufferInfos = new ArrayDeque<>(); + private final ArrayDeque<CryptoInfo> mCryptoInfos = new ArrayDeque<>(); private final ArrayList<String> mTuningKeys = new ArrayList<>(); private final ArrayList<Object> mTuningValues = new ArrayList<>(); @@ -3764,7 +3891,7 @@ final public class MediaCodec { private native void native_queueLinearBlock( int index, @NonNull LinearBlock block, - @Nullable CryptoInfo cryptoInfo, + @Nullable Object[] cryptoInfos, @NonNull Object[] bufferInfos, @NonNull ArrayList<String> keys, @NonNull ArrayList<Object> values); diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 86f89ab89f63..3174c377b91c 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -20,6 +20,7 @@ import static android.media.Utils.intersectSortedDistinctRanges; import static android.media.Utils.sortDistinctRanges; import static android.media.codec.Flags.FLAG_DYNAMIC_COLOR_ASPECTS; import static android.media.codec.Flags.FLAG_HLG_EDITING; +import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import android.annotation.FlaggedApi; @@ -778,6 +779,17 @@ public final class MediaCodecInfo { public static final String FEATURE_Roi = "region-of-interest"; /** + * <b>video decoder only</b>: codec supports detaching the + * output surface when in Surface mode. + * <p> If true, the codec can be configured in Surface mode + * without an actual surface (in detached surface mode). + * @see MediaCodec#CONFIGURE_FLAG_DETACHED_SURFACE + */ + @SuppressLint("AllUpper") + @FlaggedApi(FLAG_NULL_OUTPUT_SURFACE) + public static final String FEATURE_DetachedSurface = "detached-surface"; + + /** * Query codec feature capabilities. * <p> * These features are supported to be used by the codec. These @@ -814,6 +826,9 @@ public final class MediaCodecInfo { if (android.media.codec.Flags.dynamicColorAspects()) { features.add(new Feature(FEATURE_DynamicColorAspects, (1 << 8), true)); } + if (android.media.codec.Flags.nullOutputSurface()) { + features.add(new Feature(FEATURE_DetachedSurface, (1 << 9), true)); + } // feature to exclude codec from REGULAR codec list features.add(new Feature(FEATURE_SpecialCodec, (1 << 30), false, true)); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 7fa3ed654ebc..144b01a438a2 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -1017,7 +1017,8 @@ public final class MediaRouter2 { updateRoutesOnHandler(currentRoutes); RoutingSessionInfo oldInfo = mSystemController.getRoutingSessionInfo(); - mSystemController.setRoutingSessionInfo(currentSystemSessionInfo); + mSystemController.setRoutingSessionInfo(ensureClientPackageNameForSystemSession( + currentSystemSessionInfo, mContext.getPackageName())); if (!oldInfo.equals(currentSystemSessionInfo)) { notifyControllerUpdated(mSystemController); } @@ -1440,6 +1441,25 @@ public final class MediaRouter2 { } } + /** + * Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client + * package name} to {@code packageName} if empty and returns the session. + * + * <p>This method must only be used for {@linkplain RoutingSessionInfo#isSystemSession() + * system routing sessions}. + */ + private static RoutingSessionInfo ensureClientPackageNameForSystemSession( + @NonNull RoutingSessionInfo sessionInfo, @NonNull String packageName) { + if (!sessionInfo.isSystemSession() + || !TextUtils.isEmpty(sessionInfo.getClientPackageName())) { + return sessionInfo; + } + + return new RoutingSessionInfo.Builder(sessionInfo) + .setClientPackageName(packageName) + .build(); + } + /** Callback for receiving events about media route discovery. */ public abstract static class RouteCallback { /** @@ -2891,25 +2911,6 @@ public final class MediaRouter2 { } /** - * Sets the routing session's {@linkplain RoutingSessionInfo#getClientPackageName() client - * package name} to {@link #mClientPackageName} if empty and returns the session. - * - * <p>This method must only be used for {@linkplain RoutingSessionInfo#isSystemSession() - * system routing sessions}. - */ - private RoutingSessionInfo ensureClientPackageNameForSystemSession( - RoutingSessionInfo sessionInfo) { - if (!sessionInfo.isSystemSession() - || !TextUtils.isEmpty(sessionInfo.getClientPackageName())) { - return sessionInfo; - } - - return new RoutingSessionInfo.Builder(sessionInfo) - .setClientPackageName(mClientPackageName) - .build(); - } - - /** * Requests the release of a {@linkplain RoutingSessionInfo routing session}. Calls {@link * #onSessionReleasedOnHandler(RoutingSessionInfo)} on success. * @@ -2997,7 +2998,7 @@ public final class MediaRouter2 { RoutingController oldController; if (oldSession.isSystemSession()) { mSystemController.setRoutingSessionInfo( - ensureClientPackageNameForSystemSession(oldSession)); + ensureClientPackageNameForSystemSession(oldSession, mClientPackageName)); oldController = mSystemController; } else { oldController = new RoutingController(oldSession); @@ -3006,7 +3007,7 @@ public final class MediaRouter2 { RoutingController newController; if (newSession.isSystemSession()) { mSystemController.setRoutingSessionInfo( - ensureClientPackageNameForSystemSession(newSession)); + ensureClientPackageNameForSystemSession(newSession, mClientPackageName)); newController = mSystemController; } else { newController = new RoutingController(newSession); @@ -3033,7 +3034,7 @@ public final class MediaRouter2 { RoutingController controller; if (session.isSystemSession()) { mSystemController.setRoutingSessionInfo( - ensureClientPackageNameForSystemSession(session)); + ensureClientPackageNameForSystemSession(session, mClientPackageName)); controller = mSystemController; } else { controller = new RoutingController(session); @@ -3303,7 +3304,8 @@ public final class MediaRouter2 { public RoutingSessionInfo getSystemSessionInfo() { RoutingSessionInfo currentSystemSessionInfo = null; try { - currentSystemSessionInfo = mMediaRouterService.getSystemSessionInfo(); + currentSystemSessionInfo = ensureClientPackageNameForSystemSession( + mMediaRouterService.getSystemSessionInfo(), mContext.getPackageName()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig index c4b38c725ea4..b16580927fa6 100644 --- a/media/java/android/media/flags/projection.aconfig +++ b/media/java/android/media/flags/projection.aconfig @@ -1,4 +1,4 @@ -package: "com.android.media.flags" +package: "com.android.media.projection.flags" # Project link: https://gantry.corp.google.com/projects/android_platform_window_surfaces/changes diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java index eb980a385cd3..9035cb78f132 100644 --- a/media/java/android/media/tv/BroadcastInfoRequest.java +++ b/media/java/android/media/tv/BroadcastInfoRequest.java @@ -85,6 +85,8 @@ public abstract class BroadcastInfoRequest implements Parcelable { return CommandRequest.createFromParcelBody(source); case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE: return TimelineRequest.createFromParcelBody(source); + case TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA: + return SignalingDataRequest.createFromParcelBody(source); default: throw new IllegalStateException( "Unexpected broadcast info request type (value " diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java index 4ba2b2c2ff7b..f589244c1da1 100644 --- a/media/java/android/media/tv/BroadcastInfoResponse.java +++ b/media/java/android/media/tv/BroadcastInfoResponse.java @@ -71,6 +71,8 @@ public abstract class BroadcastInfoResponse implements Parcelable { return CommandResponse.createFromParcelBody(source); case TvInputManager.BROADCAST_INFO_TYPE_TIMELINE: return TimelineResponse.createFromParcelBody(source); + case TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA: + return SignalingDataResponse.createFromParcelBody(source); default: throw new IllegalStateException( "Unexpected broadcast info response type (value " diff --git a/media/java/android/media/tv/SignalingDataInfo.java b/media/java/android/media/tv/SignalingDataInfo.java index b29ea5c905ec..e5231fcc704f 100644 --- a/media/java/android/media/tv/SignalingDataInfo.java +++ b/media/java/android/media/tv/SignalingDataInfo.java @@ -16,11 +16,19 @@ package android.media.tv; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.media.tv.flags.Flags; import android.os.Parcelable; -/** @hide */ -public class SignalingDataInfo implements Parcelable { +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Describes a metadata object of a {@link SignalingDataResponse}. + */ +@FlaggedApi(Flags.FLAG_TIAF_V_APIS) +public final class SignalingDataInfo implements Parcelable { public static final @NonNull Parcelable.Creator<SignalingDataInfo> CREATOR = new Parcelable.Creator<SignalingDataInfo>() { @Override @@ -34,59 +42,131 @@ public class SignalingDataInfo implements Parcelable { } }; - private int mTableId; - private @NonNull String mTable; - private int mMetadataType; - private int mVersion; - private int mGroup; - private @NonNull String mEncoding; + private final @NonNull String mTable; + private final @NonNull @SignalingDataRequest.SignalingMetadata String mSignalingDataType; + private final int mVersion; + private final int mGroup; + private final @NonNull String mEncoding; + + /** + * This value for {@link #getGroup()} denotes that there's no group associated with this + * metadata. + */ + public static final int LLS_NO_GROUP_ID = -1; + + /** + * The encoding of the content is UTF-8. This is the default value. + */ + public static final String CONTENT_ENCODING_UTF_8 = "UTF-8"; + + /** + * A/344:2023-5 9.2.10 compliant string for when the encoding of the content is Base64. + */ + public static final String CONTENT_ENCODING_BASE64 = "Base64"; + + /** + * @hide + */ + @android.annotation.StringDef(prefix = "CONTENT_ENCODING_", value = { + CONTENT_ENCODING_UTF_8, + CONTENT_ENCODING_BASE64 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ContentEncoding {} public SignalingDataInfo( - int tableId, @NonNull String table, - int metadataType, + @NonNull String signalingDataType, + int version, + int group) { + this.mTable = table; + com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable); + this.mSignalingDataType = signalingDataType; + this.mVersion = version; + this.mGroup = group; + this.mEncoding = CONTENT_ENCODING_UTF_8; + } + + public SignalingDataInfo( + @NonNull String table, + @NonNull String signalingDataType, int version, int group, @NonNull String encoding) { - this.mTableId = tableId; this.mTable = table; com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable); - this.mMetadataType = metadataType; + this.mSignalingDataType = signalingDataType; this.mVersion = version; this.mGroup = group; this.mEncoding = encoding; com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mEncoding); } - public int getTableId() { - return mTableId; - } - - public @NonNull String getTable() { + /** + * The signaling table data, represented as a XML, JSON or BASE64 string. + * + * <p> For more details on how this data is formatted refer to the ATSC standard + * A/344:2023-5 9.2.10 - Query Signaling Data API. + * + * @return The signaling table data. + */ + @NonNull + public String getTable() { return mTable; } - public int getMetadataType() { - return mMetadataType; + /** + * Gets the signaling data type contained in this metadata object. This may be either a + * LLS Metadata Object or a SLS Metadata Object name. + * + * <p>For more details on each type of metadata that can be requested, refer to the ATSC + * standard A/344:2023-5 9.2.10 - Query Signaling Data API. + * + * @return the type of metadata in this metadata object + */ + @NonNull + public @SignalingDataRequest.SignalingMetadata String getSignalingDataType() { + return mSignalingDataType; } + /** + * Gets the version of the signalling element. For LLS, this should be the + * LLS_table_version. For SLS Metadata Objects, this should be metadataEnvelope@version. + * + * For more details on where this version comes from, refer to the ATSC 3.0 + * standard A/344:2023-5 9.2.10 - Query Signaling Data API. + * + * @return The version of the signalling element. + */ public int getVersion() { return mVersion; } + /** + * Gets the LLS group ID. Required for LLS Tables. For SLS Metadata Objects, this should be + * {@link #LLS_NO_GROUP_ID}. + * + * @return the LLS group ID. + */ public int getGroup() { return mGroup; } - public @NonNull String getEncoding() { + /** + * The content encoding of the data. This value defaults to {@link #CONTENT_ENCODING_UTF_8}. + * + * <p> Can be either {@link #CONTENT_ENCODING_BASE64} or {@link #CONTENT_ENCODING_UTF_8}. + * @return The content encoding of the data. + */ + @NonNull + public @ContentEncoding String getEncoding() { return mEncoding; } @Override public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - dest.writeInt(mTableId); dest.writeString(mTable); - dest.writeInt(mMetadataType); + dest.writeString(mSignalingDataType); dest.writeInt(mVersion); dest.writeInt(mGroup); dest.writeString(mEncoding); @@ -98,17 +178,17 @@ public class SignalingDataInfo implements Parcelable { } SignalingDataInfo(@NonNull android.os.Parcel in) { - int tableId = in.readInt(); String table = in.readString(); - int metadataType = in.readInt(); + String metadataType = in.readString(); int version = in.readInt(); int group = in.readInt(); String encoding = in.readString(); - this.mTableId = tableId; this.mTable = table; com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTable); - this.mMetadataType = metadataType; + this.mSignalingDataType = metadataType; + com.android.internal.util.AnnotationValidations + .validate(NonNull.class, null, mSignalingDataType); this.mVersion = version; this.mGroup = group; this.mEncoding = encoding; diff --git a/media/java/android/media/tv/SignalingDataRequest.java b/media/java/android/media/tv/SignalingDataRequest.java index dcf1d48aaf3a..9874517a9085 100644 --- a/media/java/android/media/tv/SignalingDataRequest.java +++ b/media/java/android/media/tv/SignalingDataRequest.java @@ -16,19 +16,27 @@ package android.media.tv; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.media.tv.flags.Flags; +import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.List; + /** * Request to retrieve the Low-level Signalling Tables (LLS) and Service-layer Signalling (SLS) * metadata. * - * <p>For more details on each type of metadata that can be requested, refer to the ATSC standard + * <p> For more details on each type of metadata that can be requested, refer to the ATSC standard * A/344:2023-5 9.2.10 - Query Signaling Data API. * - * @hide + * @see SignalingDataResponse */ -public class SignalingDataRequest extends BroadcastInfoRequest implements Parcelable { +@FlaggedApi(Flags.FLAG_TIAF_V_APIS) +public final class SignalingDataRequest extends BroadcastInfoRequest implements Parcelable { private static final @TvInputManager.BroadcastInfoType int REQUEST_TYPE = TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA; @@ -45,126 +53,220 @@ public class SignalingDataRequest extends BroadcastInfoRequest implements Parcel } }; - /** SLS Metadata: All metadata objects for the requested service(s) */ - public static final int SLS_METADATA_ALL = 0x7FFFFFF; /** SLS Metadata: APD for the requested service(s) */ - public static final int SLS_METADATA_APD = 1; + public static final String SIGNALING_METADATA_APD = "APD"; /** SLS Metadata: USBD for the requested service(s) */ - public static final int SLS_METADATA_USBD = 1 << 1; + public static final String SIGNALING_METADATA_USBD = "USBD"; /** SLS Metadata: S-TSID for the requested service(s) */ - public static final int SLS_METADATA_STSID = 1 << 2; + public static final String SIGNALING_METADATA_STSID = "STSID"; /** SLS Metadata: DASH MPD for the requested service(s) */ - public static final int SLS_METADATA_MPD = 1 << 3; + public static final String SIGNALING_METADATA_MPD = "MPD"; /** SLS Metadata: User Service Description for MMTP */ - public static final int SLS_METADATA_USD = 1 << 4; + public static final String SIGNALING_METADATA_USD = "USD"; /** SLS Metadata: MMT Package Access Table for the requested service(s) */ - public static final int SLS_METADATA_PAT = 1 << 5; + public static final String SIGNALING_METADATA_PAT = "PAT"; /** SLS Metadata: MMT Package Table for the requested service(s) */ - public static final int SLS_METADATA_MPT = 1 << 6; + public static final String SIGNALING_METADATA_MPT = "MPT"; /** SLS Metadata: MMT Media Presentation Information Table for the requested service(s) */ - public static final int SLS_METADATA_MPIT = 1 << 7; + public static final String SIGNALING_METADATA_MPIT = "MPIT"; /** SLS Metadata: MMT Clock Relation Information for the requested service(s) */ - public static final int SLS_METADATA_CRIT = 1 << 8; + public static final String SIGNALING_METADATA_CRIT = "CRIT"; /** SLS Metadata: MMT Device Capabilities Information Table for the requested service(s) */ - public static final int SLS_METADATA_DCIT = 1 << 9; + public static final String SIGNALING_METADATA_DCIT = "DCIT"; /** SLS Metadata: HTML Entry Pages Location Description for the requested service(s) */ - public static final int SLS_METADATA_HELD = 1 << 10; + public static final String SIGNALING_METADATA_HELD = "HELD"; /** SLS Metadata: Distribution Window Desciription for the requested service(s) */ - public static final int SLS_METADATA_DWD = 1 << 11; + public static final String SIGNALING_METADATA_DWD = "DWD"; /** SLS Metadata: MMT Application Event Information for the requested service(s) */ - public static final int SLS_METADATA_AEI = 1 << 12; + public static final String SIGNALING_METADATA_AEI = "AEI"; /** SLS Metadata: Video Stream Properties Descriptor */ - public static final int SLS_METADATA_VSPD = 1 << 13; + public static final String SIGNALING_METADATA_VSPD = "VSPD"; /** SLS Metadata: ATSC Staggercast Descriptor */ - public static final int SLS_METADATA_ASD = 1 << 14; + public static final String SIGNALING_METADATA_ASD = "ASD"; /** SLS Metadata: Inband Event Descriptor */ - public static final int SLS_METADATA_IED = 1 << 15; + public static final String SIGNALING_METADATA_IED = "IED"; /** SLS Metadata: Caption Asset Descriptor */ - public static final int SLS_METADATA_CAD = 1 << 16; + public static final String SIGNALING_METADATA_CAD = "CAD"; /** SLS Metadata: Audio Stream Properties Descriptor */ - public static final int SLS_METADATA_ASPD = 1 << 17; + public static final String SIGNALING_METADATA_ASPD = "ASPD"; /** SLS Metadata: Security Properties Descriptor */ - public static final int SLS_METADATA_SSD = 1 << 18; + public static final String SIGNALING_METADATA_SSD = "SSD"; /** SLS Metadata: ROUTE/DASH Application Dynamic Event for the requested service(s) */ - public static final int SLS_METADATA_EMSG = 1 << 19; + public static final String SIGNALING_METADATA_EMSG = "EMSG"; /** SLS Metadata: MMT Application Dynamic Event for the requested service(s) */ - public static final int SLS_METADATA_EVTI = 1 << 20; + public static final String SIGNALING_METADATA_EVTI = "EVTI"; - /** Regional Service Availability Table for the requested service(s) */ - public static final int SLS_METADATA_RSAT = 1 << 21; + /** SLS Metadata: Regional Service Availability Table for the requested service(s) */ + public static final String SIGNALING_METADATA_RSAT = "RSAT"; - private final int mGroup; - private @NonNull final int[] mLlsTableIds; - private final int mSlsMetadataTypes; + /** SLS Metadata: Recovery Data Table for the requested service(s) */ + public static final String SIGNALING_METADATA_RDT = "RDT"; - SignalingDataRequest( - int requestId, - int option, - int group, - @NonNull int[] llsTableIds, - int slsMetadataTypes) { - super(REQUEST_TYPE, requestId, option); - mGroup = group; - mLlsTableIds = llsTableIds; - mSlsMetadataTypes = slsMetadataTypes; - } + /** + * Service List Table for the requested service(s), LLS_table_id = 1. + */ + public static final String SIGNALING_METADATA_SLT = "SLT"; - SignalingDataRequest(@NonNull android.os.Parcel in) { - super(REQUEST_TYPE, in); + /** + * Region Rating Table for the requested service(s), LLS_table_id = 2. + */ + public static final String SIGNALING_METADATA_RRT = "RRT"; - int group = in.readInt(); - int[] llsTableIds = in.createIntArray(); - int slsMetadataTypes = in.readInt(); + /** + * System Time Table for the requested service(s), LLS_table_id = 3. + */ + public static final String SIGNALING_METADATA_STT = "STT"; + /** + * Advance Emergency Information Table for the requested service(s), LLS_table_id = 4. + */ + public static final String SIGNALING_METADATA_AEAT = "AEAT"; + + /** + * Onscreen Message Notifications for the requested service(s), LLS_table_id = 5. + */ + public static final String SIGNALING_METADATA_OSN = "OSN"; + + /** + * Signed Multitable for the requested service(s), LLS_table_id = 0xFE (254). + */ + public static final String SIGNALING_METADATA_SMT = "SMT"; + + /** + * CertificateData Tablefor the requested service(s), LLS_table_id = 6. + */ + public static final String SIGNALING_METADATA_CDT = "CDT"; + + private final int mGroup; + private final @NonNull List<String> mSignalingDataTypes; + + /** + * Denotes that theres no group associated with this request. + */ + public static final int SIGNALING_DATA_NO_GROUP_ID = -1; + + /** + * @hide + */ + @android.annotation.StringDef(prefix = "SIGNALING_METADATA_", value = { + SIGNALING_METADATA_APD, + SIGNALING_METADATA_USBD, + SIGNALING_METADATA_STSID, + SIGNALING_METADATA_MPD, + SIGNALING_METADATA_USD, + SIGNALING_METADATA_PAT, + SIGNALING_METADATA_MPT, + SIGNALING_METADATA_MPIT, + SIGNALING_METADATA_CRIT, + SIGNALING_METADATA_DCIT, + SIGNALING_METADATA_HELD, + SIGNALING_METADATA_DWD, + SIGNALING_METADATA_AEI, + SIGNALING_METADATA_VSPD, + SIGNALING_METADATA_ASD, + SIGNALING_METADATA_IED, + SIGNALING_METADATA_CAD, + SIGNALING_METADATA_ASPD, + SIGNALING_METADATA_SSD, + SIGNALING_METADATA_EMSG, + SIGNALING_METADATA_EVTI, + SIGNALING_METADATA_RSAT, + SIGNALING_METADATA_RDT, + SIGNALING_METADATA_SLT, + SIGNALING_METADATA_RRT, + SIGNALING_METADATA_STT, + SIGNALING_METADATA_AEAT, + SIGNALING_METADATA_OSN, + SIGNALING_METADATA_SMT, + SIGNALING_METADATA_CDT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SignalingMetadata {} + + public SignalingDataRequest(int requestId, @RequestOption int option, + int group, + @NonNull List<String> signalingDataTypes) { + super(REQUEST_TYPE, requestId, option); this.mGroup = group; - this.mLlsTableIds = llsTableIds; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mLlsTableIds); - this.mSlsMetadataTypes = slsMetadataTypes; + this.mSignalingDataTypes = signalingDataTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSignalingDataTypes); } + static SignalingDataRequest createFromParcelBody(Parcel in) { + return new SignalingDataRequest(in); + } + + /** + * Gets the group with which any signaling returned will be associated. + * + * <p> Requested metadata objects will only be returned if they are part of the group specified. + * + * <p> If no group is specified ({@link #SIGNALING_DATA_NO_GROUP_ID}), + * the receiver will send all the metadata objects discovered. + * + * @return The group ID which any signaling returned will be associated. + */ public int getGroup() { return mGroup; } - public @NonNull int[] getLlsTableIds() { - return mLlsTableIds; + /** + * Gets the signaling data types for which data should be retrieved. + * + * <p> For more details on each type of metadata that can be requested, refer to the ATSC + * standard A/344:2023-5 9.2.10 - Query Signaling Data API. + * + * @return The signaling data types for which data should be retrieved. + */ + public @NonNull List<String> getSignalingDataTypes() { + return mSignalingDataTypes; } - public int getSlsMetadataTypes() { - return mSlsMetadataTypes; + @Override + public int describeContents() { + return 0; } @Override public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(mGroup); - dest.writeIntArray(mLlsTableIds); - dest.writeInt(mSlsMetadataTypes); + dest.writeStringList(mSignalingDataTypes); } - @Override - public int describeContents() { - return 0; + SignalingDataRequest(@NonNull android.os.Parcel in) { + super(REQUEST_TYPE, in); + + int group = in.readInt(); + List<String> signalingDataTypes = new java.util.ArrayList<>(); + in.readStringList(signalingDataTypes); + + this.mGroup = group; + this.mSignalingDataTypes = signalingDataTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSignalingDataTypes); } + } diff --git a/media/java/android/media/tv/SignalingDataResponse.java b/media/java/android/media/tv/SignalingDataResponse.java index 3e4c790f6c70..be172ec62773 100644 --- a/media/java/android/media/tv/SignalingDataResponse.java +++ b/media/java/android/media/tv/SignalingDataResponse.java @@ -16,13 +16,24 @@ package android.media.tv; +import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.media.tv.flags.Flags; +import android.os.Parcel; import android.os.Parcelable; +import java.util.ArrayList; import java.util.List; -/** @hide */ -public class SignalingDataResponse extends BroadcastInfoResponse implements Parcelable { + +/** + * A response for the signaling data from the broadcast signal. + * + * @see SignalingDataRequest + * @see SignalingDataInfo + */ +@FlaggedApi(Flags.FLAG_TIAF_V_APIS) +public final class SignalingDataResponse extends BroadcastInfoResponse implements Parcelable { public static final @NonNull Parcelable.Creator<SignalingDataResponse> CREATOR = new Parcelable.Creator<SignalingDataResponse>() { @Override @@ -37,34 +48,44 @@ public class SignalingDataResponse extends BroadcastInfoResponse implements Parc }; private static final @TvInputManager.BroadcastInfoType int RESPONSE_TYPE = TvInputManager.BROADCAST_INFO_TYPE_SIGNALING_DATA; - private final @NonNull int[] mTableIds; - private final int mMetadataTypes; + private final @NonNull List<String> mSignalingDataTypes; private final @NonNull List<SignalingDataInfo> mSignalingDataInfoList; + static SignalingDataResponse createFromParcelBody(Parcel in) { + return new SignalingDataResponse(in); + } + public SignalingDataResponse( int requestId, int sequence, @ResponseResult int responseResult, - @NonNull int[] tableIds, - int metadataTypes, + @NonNull List<String> signalingDataTypes, @NonNull List<SignalingDataInfo> signalingDataInfoList) { super(RESPONSE_TYPE, requestId, sequence, responseResult); - this.mTableIds = tableIds; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds); - this.mMetadataTypes = metadataTypes; + mSignalingDataTypes = signalingDataTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSignalingDataTypes); this.mSignalingDataInfoList = signalingDataInfoList; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mSignalingDataInfoList); } - public @NonNull int[] getTableIds() { - return mTableIds; - } - - public int getMetadataTypes() { - return mMetadataTypes; + /** + * Gets a list of types of metadata that are contained in this response. + * + * <p> A list of types available are defined in {@link SignalingDataRequest}. + * For more information about these types, see A/344:2023-5 9.2.10 - Query Signaling Data API. + * + * @return A list of types of metadata that are contained in this response. + */ + public @NonNull List<String> getSignalingDataTypes() { + return mSignalingDataTypes; } + /** + * Gets a list of {@link SignalingDataInfo} contained in this response. + * @return A list of {@link SignalingDataInfo} contained in this response. + */ public @NonNull List<SignalingDataInfo> getSignalingDataInfoList() { return mSignalingDataInfoList; } @@ -72,8 +93,7 @@ public class SignalingDataResponse extends BroadcastInfoResponse implements Parc @Override public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeIntArray(mTableIds); - dest.writeInt(mMetadataTypes); + dest.writeStringList(mSignalingDataTypes); dest.writeParcelableList(mSignalingDataInfoList, flags); } @@ -85,14 +105,14 @@ public class SignalingDataResponse extends BroadcastInfoResponse implements Parc SignalingDataResponse(@NonNull android.os.Parcel in) { super(RESPONSE_TYPE, in); - int[] tableIds = in.createIntArray(); - int metadataTypes = in.readInt(); + List<String> types = new ArrayList<String>(); + in.readStringList(types); List<SignalingDataInfo> signalingDataInfoList = new java.util.ArrayList<>(); in.readParcelableList(signalingDataInfoList, SignalingDataInfo.class.getClassLoader()); - this.mTableIds = tableIds; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, mTableIds); - this.mMetadataTypes = metadataTypes; + this.mSignalingDataTypes = types; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mSignalingDataTypes); this.mSignalingDataInfoList = signalingDataInfoList; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mSignalingDataInfoList); diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index fa9afa872091..a8e94234e063 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -232,52 +232,8 @@ public abstract class MediaBrowserService extends Service { + " package=" + pkg); } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - // Clear out the old subscriptions. We are getting new ones. - service.mServiceState.mConnections.remove(b); - - // Temporarily sets a placeholder ConnectionRecord to make - // getCurrentBrowserInfo() work in onGetRoot(). - service.mServiceState.mCurConnection = - new ConnectionRecord( - service, pkg, pid, uid, rootHints, callbacks, null); - BrowserRoot root = service.onGetRoot(pkg, uid, rootHints); - service.mServiceState.mCurConnection = null; - - // If they didn't return something, don't allow this client. - if (root == null) { - Log.i(TAG, "No root for client " + pkg + " from service " - + getClass().getName()); - try { - callbacks.onConnectFailed(); - } catch (RemoteException ex) { - Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. " - + "pkg=" + pkg); - } - } else { - try { - ConnectionRecord connection = - new ConnectionRecord( - service, pkg, pid, uid, rootHints, callbacks, root); - service.mServiceState.mConnections.put(b, connection); - b.linkToDeath(connection, 0); - if (service.mServiceState.mSession != null) { - callbacks.onConnect( - connection.root.getRootId(), - service.mServiceState.mSession, - connection.root.getExtras()); - } - } catch (RemoteException ex) { - Log.w(TAG, "Calling onConnect() failed. Dropping client. " - + "pkg=" + pkg); - service.mServiceState.mConnections.remove(b); - } - } - } - }); + service.mHandler.post( + () -> service.connectOnHandler(pkg, pid, uid, rootHints, callbacks)); } @Override @@ -315,22 +271,8 @@ public abstract class MediaBrowserService extends Service { return; } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - - // Get the record for the connection - ConnectionRecord connection = service.mServiceState.mConnections.get(b); - if (connection == null) { - Log.w(TAG, "addSubscription for callback that isn't registered id=" - + id); - return; - } - - service.addSubscription(id, connection, token, options); - } - }); + service.mHandler.post( + () -> service.addSubscriptionOnHandler(id, callbacks, token, options)); } @Override @@ -347,23 +289,12 @@ public abstract class MediaBrowserService extends Service { return; } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - - ConnectionRecord connection = service.mServiceState.mConnections.get(b); - if (connection == null) { - Log.w(TAG, "removeSubscription for callback that isn't registered id=" - + id); - return; - } - if (!service.removeSubscription(id, connection, token)) { - Log.w(TAG, "removeSubscription called for " + id - + " which is not subscribed"); - } - } - }); + service.mHandler.post( + () -> { + if (!service.removeSubscriptionOnHandler(id, callbacks, token)) { + Log.w(TAG, "removeSubscription for id with no subscription: " + id); + } + }); } @Override @@ -374,18 +305,8 @@ public abstract class MediaBrowserService extends Service { return; } - service.mHandler.post(new Runnable() { - @Override - public void run() { - final IBinder b = callbacks.asBinder(); - ConnectionRecord connection = service.mServiceState.mConnections.get(b); - if (connection == null) { - Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId); - return; - } - service.performLoadItem(mediaId, connection, receiver); - } - }); + service.mHandler.post( + () -> service.performLoadItemOnHandler(mediaId, callbacks, receiver)); } } @@ -527,22 +448,7 @@ public abstract class MediaBrowserService extends Service { throw new IllegalStateException("The session token has already been set."); } mServiceState.mSession = token; - mHandler.post(new Runnable() { - @Override - public void run() { - Iterator<ConnectionRecord> iter = mServiceState.mConnections.values().iterator(); - while (iter.hasNext()) { - ConnectionRecord connection = iter.next(); - try { - connection.callbacks.onConnect(connection.root.getRootId(), token, - connection.root.getExtras()); - } catch (RemoteException e) { - Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid."); - iter.remove(); - } - } - } - }); + mHandler.post(() -> notifySessionTokenInitializedOnHandler(token)); } /** @@ -599,7 +505,7 @@ public abstract class MediaBrowserService extends Service { * children changed. */ public void notifyChildrenChanged(@NonNull String parentId) { - notifyChildrenChangedInternal(parentId, null); + notifyChildrenChanged(parentId, Bundle.EMPTY); } /** @@ -617,30 +523,10 @@ public abstract class MediaBrowserService extends Service { if (options == null) { throw new IllegalArgumentException("options cannot be null in notifyChildrenChanged"); } - notifyChildrenChangedInternal(parentId, options); - } - - private void notifyChildrenChangedInternal(final String parentId, final Bundle options) { if (parentId == null) { throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged"); } - mHandler.post(new Runnable() { - @Override - public void run() { - for (IBinder binder : mServiceState.mConnections.keySet()) { - ConnectionRecord connection = mServiceState.mConnections.get(binder); - List<Pair<IBinder, Bundle>> callbackList = - connection.subscriptions.get(parentId); - if (callbackList != null) { - for (Pair<IBinder, Bundle> callback : callbackList) { - if (MediaBrowserUtils.hasDuplicatedItems(options, callback.second)) { - performLoadChildren(parentId, connection, callback.second); - } - } - } - } - } - }); + mHandler.post(() -> notifyChildrenChangeOnHandler(parentId, options)); } /** @@ -661,11 +547,45 @@ public abstract class MediaBrowserService extends Service { return false; } - /** - * Save the subscription and if it is a new subscription send the results. - */ - private void addSubscription(String id, ConnectionRecord connection, IBinder token, - Bundle options) { + private void notifySessionTokenInitializedOnHandler(MediaSession.Token token) { + Iterator<ConnectionRecord> iter = mServiceState.mConnections.values().iterator(); + while (iter.hasNext()) { + ConnectionRecord connection = iter.next(); + try { + connection.callbacks.onConnect( + connection.root.getRootId(), token, connection.root.getExtras()); + } catch (RemoteException e) { + Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid."); + iter.remove(); + } + } + } + + private void notifyChildrenChangeOnHandler(final String parentId, final Bundle options) { + for (IBinder binder : mServiceState.mConnections.keySet()) { + ConnectionRecord connection = mServiceState.mConnections.get(binder); + List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(parentId); + if (callbackList != null) { + for (Pair<IBinder, Bundle> callback : callbackList) { + if (MediaBrowserUtils.hasDuplicatedItems(options, callback.second)) { + performLoadChildrenOnHandler(parentId, connection, callback.second); + } + } + } + } + } + + /** Save the subscription and if it is a new subscription send the results. */ + private void addSubscriptionOnHandler( + String id, IMediaBrowserServiceCallbacks callbacks, IBinder token, Bundle options) { + IBinder b = callbacks.asBinder(); + // Get the record for the connection + ConnectionRecord connection = mServiceState.mConnections.get(b); + if (connection == null) { + Log.w(TAG, "addSubscription for callback that isn't registered id=" + id); + return; + } + // Save the subscription List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id); if (callbackList == null) { @@ -680,13 +600,66 @@ public abstract class MediaBrowserService extends Service { callbackList.add(new Pair<>(token, options)); connection.subscriptions.put(id, callbackList); // send the results - performLoadChildren(id, connection, options); + performLoadChildrenOnHandler(id, connection, options); } - /** - * Remove the subscription. - */ - private boolean removeSubscription(String id, ConnectionRecord connection, IBinder token) { + private void connectOnHandler( + String pkg, + int pid, + int uid, + Bundle rootHints, + IMediaBrowserServiceCallbacks callbacks) { + IBinder b = callbacks.asBinder(); + // Clear out the old subscriptions. We are getting new ones. + mServiceState.mConnections.remove(b); + + // Temporarily sets a placeholder ConnectionRecord to make getCurrentBrowserInfo() work in + // onGetRoot(). + mServiceState.mCurConnection = + new ConnectionRecord( + /* service= */ this, pkg, pid, uid, rootHints, callbacks, /* root= */ null); + BrowserRoot root = onGetRoot(pkg, uid, rootHints); + mServiceState.mCurConnection = null; + + // If they didn't return something, don't allow this client. + if (root == null) { + Log.i(TAG, "No root for client " + pkg + " from service " + getClass().getName()); + try { + callbacks.onConnectFailed(); + } catch (RemoteException ex) { + Log.w(TAG, "Calling onConnectFailed() failed. Ignoring. pkg=" + pkg); + } + } else { + try { + ConnectionRecord connection = + new ConnectionRecord( + /* service= */ this, pkg, pid, uid, rootHints, callbacks, root); + mServiceState.mConnections.put(b, connection); + b.linkToDeath(connection, /* flags= */ 0); + if (mServiceState.mSession != null) { + callbacks.onConnect( + connection.root.getRootId(), + mServiceState.mSession, + connection.root.getExtras()); + } + } catch (RemoteException ex) { + Log.w(TAG, "Calling onConnect() failed. Dropping client. pkg=" + pkg); + mServiceState.mConnections.remove(b); + } + } + } + + /** Remove the subscription. */ + private boolean removeSubscriptionOnHandler( + String id, IMediaBrowserServiceCallbacks callbacks, IBinder token) { + final IBinder b = callbacks.asBinder(); + + ConnectionRecord connection = mServiceState.mConnections.get(b); + if (connection == null) { + Log.w(TAG, "removeSubscription for callback that isn't registered id=" + id); + return true; + } + if (token == null) { return connection.subscriptions.remove(id) != null; } @@ -700,7 +673,7 @@ public abstract class MediaBrowserService extends Service { iter.remove(); } } - if (callbackList.size() == 0) { + if (callbackList.isEmpty()) { connection.subscriptions.remove(id); } } @@ -709,44 +682,53 @@ public abstract class MediaBrowserService extends Service { /** * Call onLoadChildren and then send the results back to the connection. - * <p> - * Callers must make sure that this connection is still connected. + * + * <p>Callers must make sure that this connection is still connected. */ - private void performLoadChildren(final String parentId, final ConnectionRecord connection, - final Bundle options) { + private void performLoadChildrenOnHandler( + final String parentId, final ConnectionRecord connection, final Bundle options) { final Result<List<MediaBrowser.MediaItem>> result = - new Result<List<MediaBrowser.MediaItem>>(parentId) { - @Override - void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) { - if (mServiceState.mConnections.get(connection.callbacks.asBinder()) != connection) { - if (DBG) { - Log.d(TAG, "Not sending onLoadChildren result for connection that has" - + " been disconnected. pkg=" + connection.pkg + " id=" + parentId); - } - return; - } + new Result<>(parentId) { + @Override + void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) { + if (mServiceState.mConnections.get(connection.callbacks.asBinder()) + != connection) { + if (DBG) { + Log.d( + TAG, + "Not sending onLoadChildren result for connection that has" + + " been disconnected. pkg=" + + connection.pkg + + " id=" + + parentId); + } + return; + } - List<MediaBrowser.MediaItem> filteredList = - (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0 - ? MediaBrowserUtils.applyPagingOptions(list, options) : list; - final ParceledListSlice<MediaBrowser.MediaItem> pls; - if (filteredList == null) { - pls = null; - } else { - pls = new ParceledListSlice<>(filteredList); - // Limit the size of initial Parcel to prevent binder buffer overflow - // as onLoadChildren is an async binder call. - pls.setInlineCountLimit(1); - } - try { - connection.callbacks.onLoadChildren(parentId, pls, options); - } catch (RemoteException ex) { - // The other side is in the process of crashing. - Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId - + " package=" + connection.pkg); - } - } - }; + List<MediaBrowser.MediaItem> filteredList = + (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0 + ? MediaBrowserUtils.applyPagingOptions(list, options) + : list; + ParceledListSlice<MediaBrowser.MediaItem> pls = null; + if (filteredList != null) { + pls = new ParceledListSlice<>(filteredList); + // Limit the size of initial Parcel to prevent binder buffer overflow + // as onLoadChildren is an async binder call. + pls.setInlineCountLimit(1); + } + try { + connection.callbacks.onLoadChildren(parentId, pls, options); + } catch (RemoteException ex) { + // The other side is in the process of crashing. + Log.w( + TAG, + "Calling onLoadChildren() failed for id=" + + parentId + + " package=" + + connection.pkg); + } + } + }; mServiceState.mCurConnection = connection; if (options == null) { @@ -762,28 +744,41 @@ public abstract class MediaBrowserService extends Service { } } - private void performLoadItem(String itemId, final ConnectionRecord connection, - final ResultReceiver receiver) { + private void performLoadItemOnHandler( + String itemId, IMediaBrowserServiceCallbacks callbacks, final ResultReceiver receiver) { + final IBinder b = callbacks.asBinder(); + ConnectionRecord connection = mServiceState.mConnections.get(b); + if (connection == null) { + Log.w(TAG, "getMediaItem for callback that isn't registered id=" + itemId); + return; + } + final Result<MediaBrowser.MediaItem> result = - new Result<MediaBrowser.MediaItem>(itemId) { - @Override - void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) { - if (mServiceState.mConnections.get(connection.callbacks.asBinder()) != connection) { - if (DBG) { - Log.d(TAG, "Not sending onLoadItem result for connection that has" - + " been disconnected. pkg=" + connection.pkg + " id=" + itemId); + new Result<>(itemId) { + @Override + void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) { + if (mServiceState.mConnections.get(connection.callbacks.asBinder()) + != connection) { + if (DBG) { + Log.d( + TAG, + "Not sending onLoadItem result for connection that has" + + " been disconnected. pkg=" + + connection.pkg + + " id=" + + itemId); + } + return; + } + if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) { + receiver.send(RESULT_ERROR, null); + return; + } + Bundle bundle = new Bundle(); + bundle.putParcelable(KEY_MEDIA_ITEM, item); + receiver.send(RESULT_OK, bundle); } - return; - } - if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) { - receiver.send(RESULT_ERROR, null); - return; - } - Bundle bundle = new Bundle(); - bundle.putParcelable(KEY_MEDIA_ITEM, item); - receiver.send(RESULT_OK, bundle); - } - }; + }; mServiceState.mCurConnection = connection; onLoadItem(itemId, result); diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 8cdd59e51ffe..8396005b1b63 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -458,6 +458,24 @@ status_t JMediaCodec::queueSecureInputBuffer( presentationTimeUs, flags, errorDetailMsg); } +status_t JMediaCodec::queueSecureInputBuffers( + size_t index, + size_t offset, + size_t size, + const sp<RefBase> &auInfos_, + const sp<RefBase> &cryptoInfos_, + AString *errorDetailMsg) { + sp<BufferInfosWrapper> auInfos((BufferInfosWrapper *)auInfos_.get()); + sp<CryptoInfosWrapper> cryptoInfos((CryptoInfosWrapper *)cryptoInfos_.get()); + return mCodec->queueSecureInputBuffers( + index, + offset, + size, + auInfos, + cryptoInfos, + errorDetailMsg); +} + status_t JMediaCodec::queueBuffer( size_t index, const std::shared_ptr<C2Buffer> &buffer, const sp<RefBase> &infos, const sp<AMessage> &tunings, AString *errorDetailMsg) { @@ -470,19 +488,16 @@ status_t JMediaCodec::queueEncryptedLinearBlock( size_t index, const sp<hardware::HidlMemory> &buffer, size_t offset, - const CryptoPlugin::SubSample *subSamples, - size_t numSubSamples, - const uint8_t key[16], - const uint8_t iv[16], - CryptoPlugin::Mode mode, - const CryptoPlugin::Pattern &pattern, + size_t size, const sp<RefBase> &infos, + const sp<RefBase> &cryptoInfos_, const sp<AMessage> &tunings, AString *errorDetailMsg) { sp<BufferInfosWrapper> auInfo((BufferInfosWrapper *)infos.get()); + sp<CryptoInfosWrapper> cryptoInfos((CryptoInfosWrapper *)cryptoInfos_.get()); return mCodec->queueEncryptedBuffer( - index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern, - auInfo, tunings, errorDetailMsg); + index, buffer, offset, size, auInfo, cryptoInfos, + tunings, errorDetailMsg); } status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) { @@ -2262,6 +2277,61 @@ struct NativeCryptoInfo { CryptoPlugin::Pattern mPattern; }; +// This class takes away all dependencies on java(env and jni) and +// could be used for taking cryptoInfo objects to MediaCodec. +struct MediaCodecCryptoInfo: public CodecCryptoInfo { + explicit MediaCodecCryptoInfo(const NativeCryptoInfo &cryptoInfo) { + if (cryptoInfo.mErr == OK) { + mNumSubSamples = cryptoInfo.mNumSubSamples; + mMode = cryptoInfo.mMode; + mPattern = cryptoInfo.mPattern; + if (cryptoInfo.mKey != nullptr) { + mKeyBuffer = ABuffer::CreateAsCopy(cryptoInfo.mKey, 16); + mKey = (uint8_t*)(mKeyBuffer.get() != nullptr ? mKeyBuffer.get()->data() : nullptr); + } + if (cryptoInfo.mIv != nullptr) { + mIvBuffer = ABuffer::CreateAsCopy(cryptoInfo.mIv, 16); + mIv = (uint8_t*)(mIvBuffer.get() != nullptr ? mIvBuffer.get()->data() : nullptr); + } + if (cryptoInfo.mSubSamples != nullptr) { + mSubSamplesBuffer = new ABuffer(sizeof(CryptoPlugin::SubSample) * mNumSubSamples); + if (mSubSamplesBuffer.get()) { + CryptoPlugin::SubSample * samples = + (CryptoPlugin::SubSample *)(mSubSamplesBuffer.get()->data()); + for (int s = 0 ; s < mNumSubSamples ; s++) { + samples[s].mNumBytesOfClearData = + cryptoInfo.mSubSamples[s].mNumBytesOfClearData; + samples[s].mNumBytesOfEncryptedData = + cryptoInfo.mSubSamples[s].mNumBytesOfEncryptedData; + } + mSubSamples = (CryptoPlugin::SubSample *)mSubSamplesBuffer.get()->data(); + } + } + + } + } + + explicit MediaCodecCryptoInfo(jint size) { + mSubSamplesBuffer = new ABuffer(sizeof(CryptoPlugin::SubSample) * 1); + mNumSubSamples = 1; + if (mSubSamplesBuffer.get()) { + CryptoPlugin::SubSample * samples = + (CryptoPlugin::SubSample *)(mSubSamplesBuffer.get()->data()); + samples[0].mNumBytesOfClearData = size; + samples[0].mNumBytesOfEncryptedData = 0; + mSubSamples = (CryptoPlugin::SubSample *)mSubSamplesBuffer.get()->data(); + } + } + ~MediaCodecCryptoInfo() {} + +protected: + // all backup buffers for the base object. + sp<ABuffer> mKeyBuffer; + sp<ABuffer> mIvBuffer; + sp<ABuffer> mSubSamplesBuffer; + +}; + static void android_media_MediaCodec_queueSecureInputBuffer( JNIEnv *env, jobject thiz, @@ -2430,6 +2500,99 @@ static void android_media_MediaCodec_queueSecureInputBuffer( codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto()); } +static status_t extractCryptoInfosFromObjectArray(JNIEnv * const env, + jint * const totalSize, + std::vector<std::unique_ptr<CodecCryptoInfo>> * const cryptoInfoObjs, + const jobjectArray &objArray, + AString * const errorDetailMsg) { + if (env == nullptr + || cryptoInfoObjs == nullptr + || totalSize == nullptr) { + if (errorDetailMsg) { + *errorDetailMsg = "Error: Null Parameters provided for extracting CryptoInfo"; + } + return BAD_VALUE; + } + const jsize numEntries = env->GetArrayLength(objArray); + if (numEntries <= 0) { + if (errorDetailMsg) { + *errorDetailMsg = "Error: No CryptoInfo found while queuing for large frame input"; + } + return BAD_VALUE; + } + cryptoInfoObjs->clear(); + *totalSize = 0; + jint size = 0; + for (jsize i = 0; i < numEntries ; i++) { + jobject param = env->GetObjectArrayElement(objArray, i); + if (param == NULL) { + if (errorDetailMsg) { + *errorDetailMsg = "Error: Null Parameters provided for extracting CryptoInfo"; + } + return BAD_VALUE; + } + NativeCryptoInfo nativeInfo(env, param); + std::unique_ptr<CodecCryptoInfo> info(new MediaCodecCryptoInfo(nativeInfo)); + for (int i = 0; i < info->mNumSubSamples; i++) { + size += info->mSubSamples[i].mNumBytesOfClearData; + size += info->mSubSamples[i].mNumBytesOfEncryptedData; + } + cryptoInfoObjs->push_back(std::move(info)); + } + *totalSize = size; + return OK; +} + + +static void android_media_MediaCodec_queueSecureInputBuffers( + JNIEnv *env, + jobject thiz, + jint index, + jobjectArray bufferInfosObjs, + jobjectArray cryptoInfoObjs) { + ALOGV("android_media_MediaCodec_queueSecureInputBuffers"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL || codec->initCheck() != OK) { + throwExceptionAsNecessary(env, INVALID_OPERATION, codec); + return; + } + sp<BufferInfosWrapper> auInfos = + new BufferInfosWrapper{decltype(auInfos->value)()}; + sp<CryptoInfosWrapper> cryptoInfos = + new CryptoInfosWrapper{decltype(cryptoInfos->value)()}; + AString errorDetailMsg; + jint initialOffset = 0; + jint totalSize = 0; + status_t err = extractInfosFromObject( + env, + &initialOffset, + &totalSize, + &auInfos->value, + bufferInfosObjs, + &errorDetailMsg); + if (err == OK) { + err = extractCryptoInfosFromObjectArray(env, + &totalSize, + &cryptoInfos->value, + cryptoInfoObjs, + &errorDetailMsg); + } + if (err == OK) { + err = codec->queueSecureInputBuffers( + index, + initialOffset, + totalSize, + auInfos, + cryptoInfos, + &errorDetailMsg); + } + throwExceptionAsNecessary( + env, err, ACTION_CODE_FATAL, + codec->getExceptionMessage(errorDetailMsg.c_str()).c_str(), codec->getCrypto()); +} + static jobject android_media_MediaCodec_mapHardwareBuffer(JNIEnv *env, jclass, jobject bufferObj) { ALOGV("android_media_MediaCodec_mapHardwareBuffer"); AHardwareBuffer *hardwareBuffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer( @@ -2762,7 +2925,7 @@ static void extractBufferFromContext( static void android_media_MediaCodec_native_queueLinearBlock( JNIEnv *env, jobject thiz, jint index, jobject bufferObj, - jobject cryptoInfoObj, jobjectArray objArray, jobject keys, jobject values) { + jobjectArray cryptoInfoArray, jobjectArray objArray, jobject keys, jobject values) { ALOGV("android_media_MediaCodec_native_queueLinearBlock"); sp<JMediaCodec> codec = getMediaCodec(env, thiz); @@ -2780,8 +2943,8 @@ static void android_media_MediaCodec_native_queueLinearBlock( "error occurred while converting tunings from Java to native"); return; } - jint totalSize; - jint initialOffset; + jint totalSize = 0; + jint initialOffset = 0; std::vector<AccessUnitInfo> infoVec; AString errorDetailMsg; err = extractInfosFromObject(env, @@ -2832,8 +2995,19 @@ static void android_media_MediaCodec_native_queueLinearBlock( "MediaCodec.LinearBlock#obtain method to obtain a compatible buffer."); return; } - auto cryptoInfo = - cryptoInfoObj ? NativeCryptoInfo{env, cryptoInfoObj} : NativeCryptoInfo{totalSize}; + sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()}; + jint sampleSize = 0; + if (cryptoInfoArray != nullptr) { + extractCryptoInfosFromObjectArray(env, + &sampleSize, + &cryptoInfos->value, + cryptoInfoArray, + &errorDetailMsg); + } else { + sampleSize = totalSize; + std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)}; + cryptoInfos->value.push_back(std::move(cryptoInfo)); + } if (env->ExceptionCheck()) { // Creation of cryptoInfo failed. Let the exception bubble up. return; @@ -2842,11 +3016,9 @@ static void android_media_MediaCodec_native_queueLinearBlock( index, memory, initialOffset, - cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples, - (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv, - cryptoInfo.mMode, - cryptoInfo.mPattern, + sampleSize, infos, + cryptoInfos, tunings, &errorDetailMsg); ALOGI_IF(err != OK, "queueEncryptedLinearBlock returned err = %d", err); @@ -3950,6 +4122,9 @@ static const JNINativeMethod gMethods[] = { { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V", (void *)android_media_MediaCodec_queueSecureInputBuffer }, + { "native_queueSecureInputBuffers", "(I[Ljava/lang/Object;[Ljava/lang/Object;)V", + (void *)android_media_MediaCodec_queueSecureInputBuffers }, + { "native_mapHardwareBuffer", "(Landroid/hardware/HardwareBuffer;)Landroid/media/Image;", (void *)android_media_MediaCodec_mapHardwareBuffer }, @@ -3957,7 +4132,7 @@ static const JNINativeMethod gMethods[] = { { "native_closeMediaImage", "(J)V", (void *)android_media_MediaCodec_closeMediaImage }, { "native_queueLinearBlock", - "(ILandroid/media/MediaCodec$LinearBlock;Landroid/media/MediaCodec$CryptoInfo;" + "(ILandroid/media/MediaCodec$LinearBlock;[Ljava/lang/Object;" "[Ljava/lang/Object;Ljava/util/ArrayList;Ljava/util/ArrayList;)V", (void *)android_media_MediaCodec_native_queueLinearBlock }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 02708efdea3a..abb23f516156 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -114,6 +114,14 @@ struct JMediaCodec : public AHandler { uint32_t flags, AString *errorDetailMsg); + status_t queueSecureInputBuffers( + size_t index, + size_t offset, + size_t size, + const sp<RefBase> &auInfos, + const sp<RefBase> &cryptoInfos, + AString *errorDetailMsg); + status_t queueBuffer( size_t index, const std::shared_ptr<C2Buffer> &buffer, const sp<RefBase> &infos, const sp<AMessage> &tunings, @@ -123,13 +131,9 @@ struct JMediaCodec : public AHandler { size_t index, const sp<hardware::HidlMemory> &buffer, size_t offset, - const CryptoPlugin::SubSample *subSamples, - size_t numSubSamples, - const uint8_t key[16], - const uint8_t iv[16], - CryptoPlugin::Mode mode, - const CryptoPlugin::Pattern &pattern, + size_t size, const sp<RefBase> &infos, + const sp<RefBase> &cryptoInfos, const sp<AMessage> &tunings, AString *errorDetailMsg); diff --git a/native/android/input.cpp b/native/android/input.cpp index 64e8efeaa4e8..6efb0280ac02 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -314,6 +314,23 @@ const AInputEvent* AMotionEvent_fromJava(JNIEnv* env, jobject motionEvent) { return event; } +jobject AInputEvent_toJava(JNIEnv* env, const AInputEvent* aInputEvent) { + LOG_ALWAYS_FATAL_IF(aInputEvent == nullptr, "Expected aInputEvent to be non-null"); + const int32_t eventType = AInputEvent_getType(aInputEvent); + switch (eventType) { + case AINPUT_EVENT_TYPE_MOTION: + return android::android_view_MotionEvent_obtainAsCopy(env, + static_cast<const MotionEvent&>( + *aInputEvent)); + case AINPUT_EVENT_TYPE_KEY: + return android::android_view_KeyEvent_fromNative(env, + static_cast<const KeyEvent&>( + *aInputEvent)); + default: + LOG_ALWAYS_FATAL("Unexpected event type %d in AInputEvent_toJava.", eventType); + } +} + void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper, int ident, ALooper_callbackFunc callback, void* data) { InputQueue* iq = static_cast<InputQueue*>(queue); diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt index 960510879a4c..3302265fb80c 100644 --- a/native/android/libandroid.map.txt +++ b/native/android/libandroid.map.txt @@ -90,6 +90,7 @@ LIBANDROID { AInputEvent_getSource; AInputEvent_getType; AInputEvent_release; # introduced=31 + AInputEvent_toJava; # introduced=35 AInputQueue_attachLooper; AInputQueue_detachLooper; AInputQueue_finishEvent; diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt index f9dd04b66b1f..0728daf28c25 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt @@ -40,7 +40,6 @@ fun Modifier.burnInAware( ): Modifier { val translationX by viewModel.translationX(params).collectAsState(initial = 0f) val translationY by viewModel.translationY(params).collectAsState(initial = 0f) - val alpha by viewModel.alpha.collectAsState(initial = 1f) val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel()) return this.graphicsLayer { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 3dfe65a4f736..51c008ab686a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -36,7 +36,7 @@ import com.android.systemui.plugins.clocks.ClockMetadata import com.android.systemui.plugins.clocks.ClockProvider import com.android.systemui.plugins.clocks.ClockProviderPlugin import com.android.systemui.plugins.clocks.ClockSettings -import com.android.systemui.util.Assert +import com.android.systemui.util.ThreadAssert import java.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean @@ -89,6 +89,7 @@ open class ClockRegistry( val keepAllLoaded: Boolean, subTag: String, var isTransitClockEnabled: Boolean = false, + val assert: ThreadAssert = ThreadAssert(), ) { private val TAG = "${ClockRegistry::class.simpleName} ($subTag)" private val logger: Logger = @@ -286,7 +287,7 @@ open class ClockRegistry( @OpenForTesting open fun querySettings() { - assertNotMainThread() + assert.isNotMainThread() val result = try { val json = @@ -313,7 +314,7 @@ open class ClockRegistry( @OpenForTesting open fun applySettings(value: ClockSettings?) { - assertNotMainThread() + assert.isNotMainThread() try { value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis()) @@ -339,16 +340,6 @@ open class ClockRegistry( settings = value } - @OpenForTesting - protected open fun assertMainThread() { - Assert.isMainThread() - } - - @OpenForTesting - protected open fun assertNotMainThread() { - Assert.isNotMainThread() - } - private var isClockChanged = AtomicBoolean(false) private fun triggerOnCurrentClockChanged() { val shouldSchedule = isClockChanged.compareAndSet(false, true) @@ -357,7 +348,7 @@ open class ClockRegistry( } scope.launch(mainDispatcher) { - assertMainThread() + assert.isMainThread() isClockChanged.set(false) clockChangeListeners.forEach { it.onCurrentClockChanged() } } @@ -371,7 +362,7 @@ open class ClockRegistry( } scope.launch(mainDispatcher) { - assertMainThread() + assert.isMainThread() isClockListChanged.set(false) clockChangeListeners.forEach { it.onAvailableClocksChanged() } } @@ -585,7 +576,7 @@ open class ClockRegistry( * Calling from main thread to make sure the access is thread safe. */ fun registerClockChangeListener(listener: ClockChangeListener) { - assertMainThread() + assert.isMainThread() clockChangeListeners.add(listener) } @@ -595,7 +586,7 @@ open class ClockRegistry( * Calling from main thread to make sure the access is thread safe. */ fun unregisterClockChangeListener(listener: ClockChangeListener) { - assertMainThread() + assert.isMainThread() clockChangeListeners.remove(listener) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 0b320a28b419..ef2b6f0805d6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -27,7 +27,10 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -56,11 +59,10 @@ class KeyguardInteractorTest : SysuiTestCase() { private val testScope = kosmos.testScope private val repository by lazy { kosmos.fakeKeyguardRepository } private val sceneInteractor by lazy { kosmos.sceneInteractor } - private val commandQueue by lazy { - FakeCommandQueue() - } + private val commandQueue by lazy { FakeCommandQueue() } private val bouncerRepository = FakeKeyguardBouncerRepository() private val shadeRepository = FakeShadeRepository() + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Gone)) @@ -73,6 +75,7 @@ class KeyguardInteractorTest : SysuiTestCase() { bouncerRepository = bouncerRepository, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), shadeRepository = shadeRepository, + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, ) } @@ -188,6 +191,49 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + fun dismissAlpha() = + testScope.runTest { + val dismissAlpha by collectLastValue(underTest.dismissAlpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + repository.setStatusBarState(StatusBarState.KEYGUARD) + shadeRepository.setLegacyShadeExpansion(1f) + + // When not dismissable, no alpha value (null) should emit + repository.setKeyguardDismissible(false) + assertThat(dismissAlpha).isNull() + + repository.setKeyguardDismissible(true) + assertThat(dismissAlpha).isGreaterThan(0.95f) + } + + @Test + fun dismissAlpha_whenShadeIsExpandedEmitsNull() = + testScope.runTest { + val dismissAlpha by collectLastValue(underTest.dismissAlpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + repository.setStatusBarState(StatusBarState.SHADE_LOCKED) + shadeRepository.setQsExpansion(1f) + + repository.setKeyguardDismissible(false) + assertThat(dismissAlpha).isNull() + + repository.setKeyguardDismissible(true) + assertThat(dismissAlpha).isNull() + } + + @Test fun animationDozingTransitions() = testScope.runTest { kosmos.fakeSceneContainerFlags.enabled = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt index 83782e214780..837a9db6eea7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt @@ -25,6 +25,8 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever @@ -42,67 +44,123 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) class AodAlphaViewModelTest : SysuiTestCase() { - @Mock - private lateinit var occludedToLockscreenTransitionViewModel: - OccludedToLockscreenTransitionViewModel + @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardRepository = kosmos.fakeKeyguardRepository private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val occludedToLockscreenAlpha = MutableStateFlow(0f) private lateinit var underTest: AodAlphaViewModel + private val enterFromTopAnimationAlpha = MutableStateFlow(0f) + @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(occludedToLockscreenTransitionViewModel.lockscreenAlpha) - .thenReturn(occludedToLockscreenAlpha) - kosmos.occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel + whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha) + .thenReturn(enterFromTopAnimationAlpha) + kosmos.goneToAodTransitionViewModel = goneToAodTransitionViewModel underTest = kosmos.aodAlphaViewModel } @Test - fun alpha() = + fun alpha_WhenGoneToAod() = testScope.runTest { val alpha by collectLastValue(underTest.alpha) keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.OFF, - to = KeyguardState.LOCKSCREEN, + from = KeyguardState.AOD, + to = KeyguardState.GONE, testScope = testScope, ) + assertThat(alpha).isEqualTo(0f) - keyguardRepository.setKeyguardAlpha(0.1f) - assertThat(alpha).isEqualTo(0.1f) - keyguardRepository.setKeyguardAlpha(0.5f) + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + testScope = testScope, + ) + enterFromTopAnimationAlpha.value = 0.5f assertThat(alpha).isEqualTo(0.5f) - keyguardRepository.setKeyguardAlpha(0.2f) - assertThat(alpha).isEqualTo(0.2f) - keyguardRepository.setKeyguardAlpha(0f) - assertThat(alpha).isEqualTo(0f) - occludedToLockscreenAlpha.value = 0.8f - assertThat(alpha).isEqualTo(0.8f) + + enterFromTopAnimationAlpha.value = 1f + assertThat(alpha).isEqualTo(1f) } @Test - fun alpha_whenGone_equalsZero() = + fun alpha_WhenGoneToDozing() = testScope.runTest { val alpha by collectLastValue(underTest.alpha) keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.LOCKSCREEN, + from = KeyguardState.AOD, to = KeyguardState.GONE, testScope = testScope, ) - - keyguardRepository.setKeyguardAlpha(0.1f) - assertThat(alpha).isEqualTo(0f) - keyguardRepository.setKeyguardAlpha(0.5f) assertThat(alpha).isEqualTo(0f) - keyguardRepository.setKeyguardAlpha(1f) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.DOZING, + testScope = testScope, + ) + assertThat(alpha).isEqualTo(1f) + } + + @Test + fun alpha_whenGone_equalsZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.STARTED, + ) + ) + assertThat(alpha).isNull() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.RUNNING, + value = 0.5f, + ) + ) + assertThat(alpha).isNull() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + transitionState = TransitionState.RUNNING, + value = 1f, + ) + ) assertThat(alpha).isEqualTo(0f) } + + @Test + fun enterFromTopAlpha() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + transitionState = TransitionState.STARTED, + ) + ) + + enterFromTopAnimationAlpha.value = 0.2f + assertThat(alpha).isEqualTo(0.2f) + + enterFromTopAnimationAlpha.value = 1f + assertThat(alpha).isEqualTo(1f) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index d52696a0bd87..74fa46519629 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -65,7 +65,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { clockControllerProvider = { clockController }, ) private val burnInFlow = MutableStateFlow(BurnInModel()) - private val enterFromTopAnimationAlpha = MutableStateFlow(0f) @Before fun setUp() { @@ -74,8 +73,6 @@ class AodBurnInViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) kosmos.burnInInteractor = burnInInteractor - whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha) - .thenReturn(enterFromTopAnimationAlpha) whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) .thenReturn(emptyFlow()) kosmos.goneToAodTransitionViewModel = goneToAodTransitionViewModel @@ -278,16 +275,4 @@ class AodBurnInViewModelTest : SysuiTestCase() { assertThat(translationY).isEqualTo(0) assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false)) } - - @Test - fun alpha() = - testScope.runTest { - val alpha by collectLastValue(underTest.alpha) - - enterFromTopAnimationAlpha.value = 0.2f - assertThat(alpha).isEqualTo(0.2f) - - enterFromTopAnimationAlpha.value = 1f - assertThat(alpha).isEqualTo(1f) - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index bf1d76f87f5a..e04cbfd88bb3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -255,14 +255,21 @@ class KeyguardRootViewModelTest : SysuiTestCase() { testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) + // Default value check + assertThat(alpha).isEqualTo(1f) + // Hub transition state is idle with hub open. communalRepository.setTransitionState( flowOf(ObservableCommunalTransitionState.Idle(CommunalSceneKey.Communal)) ) runCurrent() - // Set keyguard alpha to 1.0f. - keyguardInteractor.setAlpha(1.0f) + // Run at least 1 transition to make sure value remains at 0 + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) // Alpha property remains 0 regardless. assertThat(alpha).isEqualTo(0f) diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 91e6b6248d34..ee260e16dc56 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -421,9 +421,11 @@ constructor( smallTimeListener?.update(shouldTimeListenerRun) largeTimeListener?.update(shouldTimeListenerRun) - // Query ZenMode data - zenModeCallback.onZenChanged(zenModeController.zen) - zenModeCallback.onNextAlarmChanged() + bgExecutor.execute { + // Query ZenMode data + zenModeCallback.onZenChanged(zenModeController.zen) + zenModeCallback.onNextAlarmChanged() + } } fun unregisterListeners() { diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 878a5d88f164..a0f15efe7025 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -33,6 +33,7 @@ import com.android.systemui.plugins.clocks.ClockMessageBuffers; import com.android.systemui.res.R; import com.android.systemui.shared.clocks.ClockRegistry; import com.android.systemui.shared.clocks.DefaultClockProvider; +import com.android.systemui.util.ThreadAssert; import dagger.Module; import dagger.Provides; @@ -74,7 +75,8 @@ public abstract class ClockRegistryModule { clockBuffers, /* keepAllLoaded = */ false, /* subTag = */ "System", - /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK)); + /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK), + new ThreadAssert()); registry.registerListeners(); return registry; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 3397906aa6ea..0bd44f0f3901 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -17,6 +17,7 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.hardware.biometrics.Flags.customBiometricPrompt; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION; @@ -401,37 +402,8 @@ public class AuthContainerView extends LinearLayout @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper ) { - if (Utils.isBiometricAllowed(config.mPromptInfo)) { - mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication( - config.mPromptInfo, - config.mUserId, - config.mOperationId, - new BiometricModalities(fpProps, faceProps), - config.mOpPackageName); - - if (constraintBp()) { - mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null, - // TODO(b/201510778): This uses the wrong timeout in some cases - getJankListener(mLayout, TRANSIT, - BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), - mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); - } else { - final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( - R.layout.biometric_prompt_layout, null, false); - mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController, - // TODO(b/201510778): This uses the wrong timeout in some cases - getJankListener(view, TRANSIT, - BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), - mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, - vibratorHelper); - - // TODO(b/251476085): migrate these dependencies - if (fpProps != null && fpProps.isAnyUdfpsType()) { - view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps), - config.mScaleProvider); - } - } + if (Utils.isBiometricAllowed(config.mPromptInfo) || customBiometricPrompt()) { + addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true, false); } else { @@ -439,6 +411,44 @@ public class AuthContainerView extends LinearLayout } } + + private void addBiometricView(@NonNull Config config, @NonNull LayoutInflater layoutInflater, + @NonNull PromptViewModel viewModel, + @Nullable FingerprintSensorPropertiesInternal fpProps, + @Nullable FaceSensorPropertiesInternal faceProps, + @NonNull VibratorHelper vibratorHelper) { + mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication( + config.mPromptInfo, + config.mUserId, + config.mOperationId, + new BiometricModalities(fpProps, faceProps), + config.mOpPackageName); + + if (constraintBp()) { + mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null, + // TODO(b/201510778): This uses the wrong timeout in some cases + getJankListener(mLayout, TRANSIT, + BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), + mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, + vibratorHelper); + } else { + final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate( + R.layout.biometric_prompt_layout, null, false); + mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController, + // TODO(b/201510778): This uses the wrong timeout in some cases + getJankListener(view, TRANSIT, + BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS), + mBackgroundView, mBiometricCallback, mApplicationCoroutineScope, + vibratorHelper); + + // TODO(b/251476085): migrate these dependencies + if (fpProps != null && fpProps.isAnyUdfpsType()) { + view.setUdfpsAdapter(new UdfpsDialogMeasureAdapter(view, fpProps), + config.mScaleProvider); + } + } + } + private void onBackInvoked() { sendEarlyUserCanceled(); animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); @@ -524,7 +534,7 @@ public class AuthContainerView extends LinearLayout () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); if (constraintBp()) { // Do nothing on attachment with constraintLayout - } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { + } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo) || customBiometricPrompt()) { mBiometricScrollView.addView(mBiometricView.asView()); } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true /* animatePanel */, false /* animateContents */); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 31aadf51c4f2..b0cc3bd807dd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -22,6 +22,7 @@ import android.content.Context import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricPrompt +import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.face.FaceManager import android.text.method.ScrollingMovementMethod import android.util.Log @@ -123,13 +124,6 @@ object BiometricViewBinder { (view as BiometricPromptLayout).updatedFingerprintAffordanceSize } - PromptIconViewBinder.bind( - iconView, - iconOverlayView, - iconSizeOverride, - viewModel, - ) - val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator) // Negative-side (left) buttons @@ -156,6 +150,18 @@ object BiometricViewBinder { view.repeatWhenAttached { // these do not change and need to be set before any size transitions val modalities = viewModel.modalities.first() + + // If there is no biometrics available, biometric prompt is showing just for displaying + // content, no authentication needed. + if (!(customBiometricPrompt() && modalities.isEmpty)) { + PromptIconViewBinder.bind( + iconView, + iconOverlayView, + iconSizeOverride, + viewModel, + ) + } + if (modalities.hasFingerprint) { /** * Load the given [rawResources] immediately so they are cached for use in the diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index 2417fe9cd333..a37d9168dfd3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -21,6 +21,7 @@ import android.animation.AnimatorSet import android.animation.ValueAnimator import android.graphics.Outline import android.graphics.Rect +import android.hardware.biometrics.Flags import android.transition.AutoTransition import android.transition.TransitionManager import android.view.Surface @@ -59,6 +60,7 @@ import com.android.systemui.res.R import kotlin.math.abs import kotlin.math.min import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** Helper for [BiometricViewBinder] to handle resize transitions. */ @@ -219,6 +221,18 @@ object BiometricViewSizeBinder { view.repeatWhenAttached { var currentSize: PromptSize? = null + val modalities = viewModel.modalities.first() + // TODO(b/288175072): Move all visibility settings together. + // If there is no biometrics available, biometric prompt is showing just for + // displaying content, no authentication needed. + if (Flags.customBiometricPrompt() && modalities.isEmpty) { + smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) + smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + smallConstraintSet.setVisibility(R.id.indicator, View.GONE) + mediumConstraintSet.setVisibility(iconHolderView.id, View.GONE) + mediumConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) + mediumConstraintSet.setVisibility(R.id.indicator, View.GONE) + } lifecycleScope.launch { combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size) -> @@ -299,6 +313,7 @@ object BiometricViewSizeBinder { // TODO(b/251476085): migrate the legacy panel controller and simplify this view.repeatWhenAttached { var currentSize: PromptSize? = null + val modalities = viewModel.modalities.first() lifecycleScope.launch { /** * View is only set visible in BiometricViewSizeBinder once PromptSize is @@ -318,6 +333,9 @@ object BiometricViewSizeBinder { for (v in viewsToHideWhenSmall) { v.showContentOrHide(forceHide = size.isSmall) } + if (Flags.customBiometricPrompt() && modalities.isEmpty) { + iconHolderView.visibility = View.GONE + } if (currentSize == null && size.isSmall) { iconHolderView.alpha = 0f } @@ -328,8 +346,7 @@ object BiometricViewSizeBinder { // TODO(b/302735104): Fix wrong height due to the delay of // PromptContentView. addOnLayoutChangeListener() will cause crash when // showing credential view, since |PromptIconViewModel| won't release - // the - // flow. + // the flow. // propagate size changes to legacy panel controller and animate // transitions view.doOnLayout { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b5ca79e71c33..63fd6087e587 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -690,18 +690,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } else { resetStateLocked(); } - } else { - if (lastSimStateWasLocked && mShowing) { - if (DEBUG_SIM_STATES) { - Log.d(TAG, "SIM moved to " - + "NOT_READY/ABSENT/UNKNOWN when the previous state " - + "was locked. Reset the state."); - } + } + if (simState == TelephonyManager.SIM_STATE_ABSENT) { + // MVNO SIMs can become transiently NOT_READY when switching networks, + // so we should only lock when they are ABSENT. + if (lastSimStateWasLocked) { + if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the " + + "previous state was locked. Reset the state."); resetStateLocked(); } + mSimWasLocked.append(slotId, false); } - - mSimWasLocked.append(slotId, false); } break; case TelephonyManager.SIM_STATE_PIN_REQUIRED: diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index e20f570e6c0a..7593ac252543 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -150,6 +150,7 @@ constructor( when (toState) { KeyguardState.DREAMING -> TO_DREAMING_DURATION KeyguardState.AOD -> TO_AOD_DURATION + KeyguardState.DOZING -> TO_DOZING_DURATION KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds @@ -160,6 +161,7 @@ constructor( private val DEFAULT_DURATION = 500.milliseconds val TO_DREAMING_DURATION = 933.milliseconds val TO_AOD_DURATION = 1300.milliseconds + val TO_DOZING_DURATION = 933.milliseconds val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 1da0a0e5bd8c..57e9ac707965 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator +import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.systemui.communal.shared.model.CommunalSceneKey import com.android.systemui.dagger.SysUISingleton @@ -208,7 +209,10 @@ constructor( } transitionRepository.updateTransition( id, - 1f - shadeExpansion, + // This maps the shadeExpansion to a much faster curve, to match + // the existing logic + 1f - + MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, shadeExpansion), nextState, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 22d11d08054e..405d1d46456c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -37,6 +37,7 @@ import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R @@ -79,6 +80,7 @@ constructor( bouncerRepository: KeyguardBouncerRepository, configurationInteractor: ConfigurationInteractor, shadeRepository: ShadeRepository, + keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractorProvider: Provider<SceneInteractor>, ) { // TODO(b/296118689): move to a repository @@ -233,8 +235,33 @@ constructor( /** The position of the keyguard clock. */ val clockPosition: Flow<Position> = repository.clockPosition + @Deprecated("Use the relevant TransitionViewModel") val keyguardAlpha: Flow<Float> = repository.keyguardAlpha + /** + * When the lockscreen can be dismissed, emit an alpha value as the user swipes up. This is + * useful just before the code commits to moving to GONE. + */ + val dismissAlpha: Flow<Float?> = + combine( + shadeRepository.legacyShadeExpansion, + statusBarState, + keyguardTransitionInteractor.currentKeyguardState, + isKeyguardDismissible, + ) { legacyShadeExpansion, statusBarState, currentKeyguardState, isKeyguardDismissible -> + if ( + statusBarState == StatusBarState.KEYGUARD && + isKeyguardDismissible && + currentKeyguardState == LOCKSCREEN + ) { + MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, legacyShadeExpansion) + } else { + null + } + } + .onStart { emit(null) } + .distinctUntilChanged() + val keyguardTranslationY: Flow<Float> = configurationInteractor .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 310f13d49e16..d1fd7195d8cc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -56,7 +56,6 @@ class KeyguardTransitionInteractor constructor( @Application val scope: CoroutineScope, private val repository: KeyguardTransitionRepository, - private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>, private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>, private val fromPrimaryBouncerTransitionInteractor: dagger.Lazy<FromPrimaryBouncerTransitionInteractor>, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 1b7a50790561..5604ef23a142 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -179,7 +179,7 @@ object KeyguardRootViewBinder { } launch { - viewModel.lockscreenStateAlpha.collect { alpha -> + viewModel.lockscreenStateAlpha(viewState).collect { alpha -> childViews[statusViewId]?.alpha = alpha } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt index d4ea728bbffb..f208e85bde3e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt @@ -19,15 +19,15 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart /** Models UI state for the alpha of the AOD (always-on display). */ @@ -35,27 +35,28 @@ import kotlinx.coroutines.flow.onStart class AodAlphaViewModel @Inject constructor( - keyguardInteractor: KeyguardInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, - occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + goneToAodTransitionViewModel: GoneToAodTransitionViewModel, + goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel, ) { /** The alpha level for the entire lockscreen while in AOD. */ val alpha: Flow<Float> = - combine( - keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart { - emit(0f) - }, - merge( - keyguardInteractor.keyguardAlpha, - occludedToLockscreenTransitionViewModel.lockscreenAlpha, - ) - ) { transitionToGone, alpha -> - if (transitionToGone == 1f) { - // Ensures content is not visible when in GONE state - 0f - } else { - alpha + combineTransform( + keyguardTransitionInteractor.transitions, + goneToAodTransitionViewModel.enterFromTopAnimationAlpha.onStart { emit(0f) }, + goneToDozingTransitionViewModel.lockscreenAlpha.onStart { emit(0f) }, + ) { step, goneToAodAlpha, goneToDozingAlpha -> + if (step.to == GONE) { + // When transitioning to GONE, only emit a value when complete as other + // transitions may be controlling the alpha fade + if (step.value == 1f) { + emit(0f) + } + } else if (step.from == GONE && step.to == AOD) { + emit(goneToAodAlpha) + } else if (step.from == GONE && step.to == DOZING) { + emit(goneToDozingAlpha) } } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 8110de23be13..6fcbf48eab82 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -31,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED @@ -66,9 +67,6 @@ constructor( private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, private val keyguardClockViewModel: KeyguardClockViewModel, ) { - /** Alpha for elements that appear and move during the animation -> AOD */ - val alpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha - /** Horizontal translation for elements that need to apply anti-burn-in tactics. */ fun translationX( params: BurnInParameters, @@ -131,6 +129,9 @@ constructor( return combine( merge( keyguardTransitionInteractor.transition(GONE, AOD).map { it.value }, + keyguardTransitionInteractor.transition(AOD, PRIMARY_BOUNCER).map { + 1f - it.value + }, keyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, AOD).map { it.value }, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index 3a98359da2cb..a3888c3341db 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -57,21 +57,32 @@ constructor( var startValue = 0f return transitionAnimation.sharedFlowWithState( duration = 500.milliseconds, - onStart = { - startValue = currentTranslationY() ?: 0f - startValue - }, + onStart = { startValue = currentTranslationY() ?: 0f }, onStep = { MathUtils.lerp(startValue, 0f, FAST_OUT_SLOW_IN.getInterpolation(it)) }, ) } /** Ensure alpha is set to be visible */ - val lockscreenAlpha: Flow<Float> = - transitionAnimation.sharedFlow( + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var startAlpha: Float? = null + return transitionAnimation.sharedFlow( duration = 500.milliseconds, - onStart = { 1f }, - onStep = { 1f }, + onStep = { + if (startAlpha == null) { + startAlpha = viewState.alpha() + } + MathUtils.lerp(startAlpha!!, 1f, it) + }, + onFinish = { + startAlpha = null + 1f + }, + onCancel = { + startAlpha = null + 1f + }, ) + } val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( @@ -88,5 +99,10 @@ constructor( onFinish = { 1f }, ) - override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + duration = 500.milliseconds, + onStart = { 1f }, + onStep = { 1f }, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt index e4610c15a3d0..f81941bf064b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -50,6 +50,8 @@ constructor( onCancel = { 0f }, ) + val lockscreenAlpha: Flow<Float> = shortcutsAlpha + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt new file mode 100644 index 000000000000..55a289ef890f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DOZING_DURATION +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow + +/** Breaks down GONE->DOZING transition into discrete steps for corresponding views to consume. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class GoneToDozingTransitionViewModel +@Inject +constructor( + animationFlow: KeyguardTransitionAnimationFlow, +) { + + private val transitionAnimation = + animationFlow.setup( + duration = TO_DOZING_DURATION, + from = KeyguardState.GONE, + to = KeyguardState.DOZING, + ) + + val lockscreenAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + duration = 500.milliseconds, + onStep = { 0f }, + onCancel = { 1f }, + onFinish = { 1f }, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 83be65181bea..f790d356620d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -48,6 +48,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart @@ -63,15 +64,25 @@ constructor( private val communalInteractor: CommunalInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, - private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, - private val lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, private val alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, - private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, - private val lockscreenToGlanceableHubTransitionViewModel: - LockscreenToGlanceableHubTransitionViewModel, + private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, + private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel, + private val lockscreenToGlanceableHubTransitionViewModel: + LockscreenToGlanceableHubTransitionViewModel, + private val lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, + private val lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel, + private val lockscreenToPrimaryBouncerTransitionViewModel: + LockscreenToPrimaryBouncerTransitionViewModel, + private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, + private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, + private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel, + private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, + private val primaryBouncerToLockscreenTransitionViewModel: + PrimaryBouncerToLockscreenTransitionViewModel, private val screenOffAnimationController: ScreenOffAnimationController, private val aodBurnInViewModel: AodBurnInViewModel, private val aodAlphaViewModel: AodAlphaViewModel, @@ -110,13 +121,24 @@ constructor( // The transitions are mutually exclusive, so they are safe to merge to get the last // value emitted by any of them. Do not add flows that cannot make this guarantee. merge( - aodAlphaViewModel.alpha, - lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, - glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, - lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState), - primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, - alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, - ) + aodAlphaViewModel.alpha, + keyguardInteractor.dismissAlpha.filterNotNull(), + alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, + aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), + dozingToLockscreenTransitionViewModel.lockscreenAlpha, + glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, + lockscreenToDreamingTransitionViewModel.lockscreenAlpha, + lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha, + lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState), + lockscreenToOccludedTransitionViewModel.lockscreenAlpha, + lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha, + occludedToAodTransitionViewModel.lockscreenAlpha, + occludedToLockscreenTransitionViewModel.lockscreenAlpha, + primaryBouncerToAodTransitionViewModel.lockscreenAlpha, + primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, + primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, + ) + .onStart { emit(1f) } ) { isIdleOnCommunal, alpha -> if (isIdleOnCommunal) { // Keyguard should not show while the communal hub is fully visible. This check @@ -131,10 +153,13 @@ constructor( } /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */ - val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha + @Deprecated("only used for legacy status view") + fun lockscreenStateAlpha(viewState: ViewStateAccessor): Flow<Float> { + return aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState) + } /** For elements that appear and move during the animation -> AOD */ - val burnInLayerAlpha: Flow<Float> = aodBurnInViewModel.alpha + val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha fun translationY(params: BurnInParameters): Flow<Float> { return aodBurnInViewModel.translationY(params) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index ce47f3c67b21..0cfc75757b7d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -51,6 +51,8 @@ constructor( onStep = { 1f - it } ) + val lockscreenAlpha: Flow<Float> = shortcutsAlpha + override val deviceEntryParentViewAlpha: Flow<Float> = shadeDependentFlows.transitionFlow( flowWhenShadeIsNotExpanded = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt index 07c114163326..c61b1f5cc3be 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -47,6 +48,15 @@ constructor( val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + /** Lockscreen views alpha */ + val lockscreenAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + startTime = 233.milliseconds, + duration = 250.milliseconds, + onStep = { it }, + onStart = { 0f }, + ) + override val deviceEntryParentViewAlpha: Flow<Float> = deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt index 5879d180ec0f..942903bbabd7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt @@ -51,6 +51,12 @@ constructor( val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + val lockscreenAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, + onStep = { it } + ) + override val deviceEntryParentViewAlpha: Flow<Float> = deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { isUdfpsEnrolledAndEnabled -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt index 284a134f73c7..34c9ac92a3f3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor @@ -23,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow @@ -58,10 +60,13 @@ constructor( val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( - duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, + duration = 250.milliseconds, + interpolator = EMPHASIZED_ACCELERATE, onStep = { it } ) + val lockscreenAlpha: Flow<Float> = shortcutsAlpha + override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java index 3671dd439239..b4cc196b89ed 100644 --- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java @@ -39,7 +39,10 @@ public class ProcessWrapper { /** * Returns {@link UserHandle} as returned statically by {@link Process#myUserHandle()}. * - * Please strongly consider using {@link com.android.systemui.settings.UserTracker} instead. + * This should not be used to get the "current" user. This information only applies to the + * current process, not the current state of SystemUI. Please use + * {@link com.android.systemui.settings.UserTracker} if you want to learn about the currently + * active user in SystemUI. */ public UserHandle myUserHandle() { return Process.myUserHandle(); diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index 2f0fc5127009..ee602e5f1c04 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -23,6 +23,7 @@ import android.content.DialogInterface.BUTTON_NEGATIVE import android.content.DialogInterface.BUTTON_POSITIVE import android.content.Intent import android.content.Intent.EXTRA_PACKAGE_NAME +import android.content.pm.PackageManager import android.hardware.SensorPrivacyManager import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS import android.hardware.SensorPrivacyManager.EXTRA_SENSOR @@ -31,6 +32,7 @@ import android.os.Bundle import android.os.Handler import android.window.OnBackInvokedDispatcher import androidx.annotation.OpenForTesting +import com.android.internal.camera.flags.Flags import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE @@ -90,14 +92,14 @@ open class SensorUseStartedActivity @Inject constructor( sensor = ALL_SENSORS val callback = IndividualSensorPrivacyController.Callback { _, _ -> if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && - !sensorPrivacyController.isSensorBlocked(CAMERA)) { + !isCameraBlocked(sensorUsePackageName)) { finish() } } sensorPrivacyListener = callback sensorPrivacyController.addCallback(callback) if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) && - !sensorPrivacyController.isSensorBlocked(CAMERA)) { + !isCameraBlocked(sensorUsePackageName)) { finish() return } @@ -110,14 +112,22 @@ open class SensorUseStartedActivity @Inject constructor( } val callback = IndividualSensorPrivacyController.Callback { whichSensor: Int, isBlocked: Boolean -> - if (whichSensor == sensor && !isBlocked) { + if (whichSensor != sensor) { + // Ignore a callback; we're not interested in. + } else if ((whichSensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) { + finish() + } else if ((whichSensor == MICROPHONE) && !isBlocked) { finish() } } sensorPrivacyListener = callback sensorPrivacyController.addCallback(callback) - if (!sensorPrivacyController.isSensorBlocked(sensor)) { + if ((sensor == CAMERA) && !isCameraBlocked(sensorUsePackageName)) { + finish() + return + } else if ((sensor == MICROPHONE) && + !sensorPrivacyController.isSensorBlocked(MICROPHONE)) { finish() return } @@ -204,6 +214,22 @@ open class SensorUseStartedActivity @Inject constructor( recreate() } + private fun isAutomotive(): Boolean { + return getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + } + + private fun isCameraBlocked(packageName: String): Boolean { + if (Flags.cameraPrivacyAllowlist()) { + if (isAutomotive()) { + return sensorPrivacyController.isCameraPrivacyEnabled(packageName) + } else { + return sensorPrivacyController.isSensorBlocked(CAMERA) + } + } else { + return sensorPrivacyController.isSensorBlocked(CAMERA) + } + } + private fun disableSensorPrivacy() { if (sensor == ALL_SENSORS) { sensorPrivacyController.setSensorBlocked(DIALOG, MICROPHONE, false) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 296849044e33..fe45df89b526 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -71,7 +71,6 @@ import android.util.IndentingPrintWriter; import android.util.Log; import android.util.MathUtils; import android.view.HapticFeedbackConstants; -import android.view.InputDevice; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -1158,9 +1157,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Occluded->Lockscreen collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(), mOccludedToLockscreenTransition, mMainDispatcher); - collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); if (!KeyguardShadeMigrationNssl.isEnabled()) { + collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenTranslationY(), setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); @@ -1169,9 +1168,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Lockscreen->Dreaming collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(), mLockscreenToDreamingTransition, mMainDispatcher); - collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), - setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), - mMainDispatcher); + if (!KeyguardShadeMigrationNssl.isEnabled()) { + collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), + setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), + mMainDispatcher); + } collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY( mLockscreenToDreamingTransitionTranslationY), setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); @@ -1179,8 +1180,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Gone->Dreaming collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(), mGoneToDreamingTransition, mMainDispatcher); - collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + if (!KeyguardShadeMigrationNssl.isEnabled()) { + collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + } collectFlow(mView, mGoneToDreamingTransitionViewModel.lockscreenTranslationY( mGoneToDreamingTransitionTranslationY), setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); @@ -1188,16 +1191,18 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Lockscreen->Occluded collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(), mLockscreenToOccludedTransition, mMainDispatcher); - collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); if (!KeyguardShadeMigrationNssl.isEnabled()) { + collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY(), setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); } // Primary bouncer->Gone (ensures lockscreen content is not visible on successful auth) - collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(), - setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + if (!KeyguardShadeMigrationNssl.isEnabled()) { + collectFlow(mView, mPrimaryBouncerToGoneTransitionViewModel.getLockscreenAlpha(), + setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); + } } @VisibleForTesting @@ -2734,6 +2739,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateKeyguardBottomAreaAlpha() { + if (KeyguardShadeMigrationNssl.isEnabled()) { + return; + } if (mIsOcclusionTransitionRunning) { return; } @@ -5057,19 +5065,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } - final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll( - mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe( - mTrackpadGestureFeaturesEnabled, event); - - // On expanding, single mouse click expands the panel instead of dragging. - if (isFullyCollapsed() && (event.isFromSource(InputDevice.SOURCE_MOUSE) - && !isTrackpadTwoOrThreeFingerSwipe)) { - if (event.getAction() == MotionEvent.ACTION_UP) { - expand(true /* animate */); - } - return true; - } - /* * We capture touch events here and update the expand height here in case according to * the users fingers. This also handles multi-touch. @@ -5090,6 +5085,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mIgnoreXTouchSlop = true; } + final boolean isTrackpadTwoOrThreeFingerSwipe = isTrackpadScroll( + mTrackpadGestureFeaturesEnabled, event) || isTrackpadThreeFingerSwipe( + mTrackpadGestureFeaturesEnabled, event); + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: if (QuickStepContract.ALLOW_BACK_GESTURE_IN_SHADE && mAnimateBack) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt index 54b6ad71e734..fb67f7cc0b0f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt @@ -104,7 +104,7 @@ class HeadsUpCoordinator @Inject constructor( /** * Once the pipeline starts running, we can look through posted entries and quickly process - * any that don't have groups, and thus will never gave a group alert edge case. + * any that don't have groups, and thus will never gave a group heads up edge case. */ fun onBeforeTransformGroups(list: List<ListEntry>) { mNow = mSystemClock.currentTimeMillis() @@ -125,7 +125,7 @@ class HeadsUpCoordinator @Inject constructor( /** * Once we have a nearly final shade list (not including what's pruned for inflation reasons), * we know that stability and [NotifPromoter]s have been applied, so we can use the location of - * notifications in this list to determine what kind of group alert behavior should happen. + * notifications in this list to determine what kind of group heads up behavior should happen. */ fun onBeforeFinalizeFilter(list: List<ListEntry>) = mHeadsUpManager.modifyHuns { hunMutator -> // Nothing to do if there are no other adds/updates @@ -140,7 +140,7 @@ class HeadsUpCoordinator @Inject constructor( .groupBy { it.sbn.groupKey } val groupLocationsByKey: Map<String, GroupLocation> by lazy { getGroupLocationsByKey(list) } mLogger.logEvaluatingGroups(postedEntriesByGroup.size) - // For each group, determine which notification(s) for a group should alert. + // For each group, determine which notification(s) for a group should heads up. postedEntriesByGroup.forEach { (groupKey, postedEntries) -> // get and classify the logical members val logicalMembers = logicalMembersByGroup[groupKey] ?: emptyList() @@ -149,7 +149,7 @@ class HeadsUpCoordinator @Inject constructor( // Report the start of this group's evaluation mLogger.logEvaluatingGroup(groupKey, postedEntries.size, logicalMembers.size) - // If there is no logical summary, then there is no alert to transfer + // If there is no logical summary, then there is no heads up to transfer if (logicalSummary == null) { postedEntries.forEach { handlePostedEntry(it, hunMutator, scenario = "logical-summary-missing") @@ -157,43 +157,43 @@ class HeadsUpCoordinator @Inject constructor( return@forEach } - // If summary isn't wanted to be heads up, then there is no alert to transfer + // If summary isn't wanted to be heads up, then there is no heads up to transfer if (!isGoingToShowHunStrict(logicalSummary)) { postedEntries.forEach { - handlePostedEntry(it, hunMutator, scenario = "logical-summary-not-alerting") + handlePostedEntry(it, hunMutator, scenario = "logical-summary-not-heads-up") } return@forEach } - // The group is alerting! Overall goals: - // - Maybe transfer its alert to a child - // - Also let any/all newly alerting children still alert - var childToReceiveParentAlert: NotificationEntry? + // The group is heads up! Overall goals: + // - Maybe transfer its heads up to a child + // - Also let any/all newly heads up children still heads up + var childToReceiveParentHeadsUp: NotificationEntry? var targetType = "undefined" - // If the parent is alerting, always look at the posted notification with the newest + // If the parent is heads up, always look at the posted notification with the newest // 'when', and if it is isolated with GROUP_ALERT_SUMMARY, then it should receive the - // parent's alert. - childToReceiveParentAlert = - findAlertOverride(postedEntries, groupLocationsByKey::getLocation) - if (childToReceiveParentAlert != null) { - targetType = "alertOverride" + // parent's heads up. + childToReceiveParentHeadsUp = + findHeadsUpOverride(postedEntries, groupLocationsByKey::getLocation) + if (childToReceiveParentHeadsUp != null) { + targetType = "headsUpOverride" } - // If the summary is Detached and we have not picked a receiver of the alert, then we - // need to look for the best child to alert in place of the summary. + // If the summary is Detached and we have not picked a receiver of the heads up, then we + // need to look for the best child to heads up in place of the summary. val isSummaryAttached = groupLocationsByKey.contains(logicalSummary.key) - if (!isSummaryAttached && childToReceiveParentAlert == null) { - childToReceiveParentAlert = + if (!isSummaryAttached && childToReceiveParentHeadsUp == null) { + childToReceiveParentHeadsUp = findBestTransferChild(logicalMembers, groupLocationsByKey::getLocation) - if (childToReceiveParentAlert != null) { + if (childToReceiveParentHeadsUp != null) { targetType = "bestChild" } } - // If there is no child to receive the parent alert, then just handle the posted entries - // and return. - if (childToReceiveParentAlert == null) { + // If there is no child to receive the parent heads up, then just handle the posted + // entries and return. + if (childToReceiveParentHeadsUp == null) { postedEntries.forEach { handlePostedEntry(it, hunMutator, scenario = "no-transfer-target") } @@ -203,14 +203,14 @@ class HeadsUpCoordinator @Inject constructor( // At this point we just need to initiate the transfer val summaryUpdate = mPostedEntries[logicalSummary.key] - // Because we now know for certain that some child is going to alert for this summary - // (as we have found a child to transfer the alert to), mark the group as having + // Because we now know for certain that some child is going to heads up for this summary + // (as we have found a child to transfer the heads up to), mark the group as having // interrupted. This will allow us to know in the future that the "should heads up" // state of this group has already been handled, just not via the summary entry itself. logicalSummary.setInterruption() - mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentAlert.key) + mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentHeadsUp.key) - // If the summary was not attached, then remove the alert from the detached summary. + // If the summary was not attached, then remove the heads up from the detached summary. // Otherwise we can simply ignore its posted update. if (!isSummaryAttached) { val summaryUpdateForRemoval = summaryUpdate?.also { @@ -221,60 +221,63 @@ class HeadsUpCoordinator @Inject constructor( wasUpdated = false, shouldHeadsUpEver = false, shouldHeadsUpAgain = false, - isAlerting = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key), + isHeadsUpEntry = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key), isBinding = isEntryBinding(logicalSummary), ) - // If we transfer the alert and the summary isn't even attached, that means we - // should ensure the summary is no longer alerting, so we remove it here. + // If we transfer the heads up notification and the summary isn't even attached, + // that means we should ensure the summary is no longer a heads up notification, + // so we remove it here. handlePostedEntry( summaryUpdateForRemoval, hunMutator, - scenario = "detached-summary-remove-alert") + scenario = "detached-summary-remove-heads-up") } else if (summaryUpdate != null) { mLogger.logPostedEntryWillNotEvaluate( summaryUpdate, reason = "attached-summary-transferred") } - // Handle all posted entries -- if the child receiving the parent's alert is in the - // list, then set its flags to ensure it alerts. - var didAlertChildToReceiveParentAlert = false + // Handle all posted entries -- if the child receiving the parent's heads up is in the + // list, then set its flags to ensure it heads up. + var didHeadsUpChildToReceiveParentHeadsUp = false postedEntries.asSequence() .filter { it.key != logicalSummary.key } .forEach { postedEntry -> - if (childToReceiveParentAlert.key == postedEntry.key) { + if (childToReceiveParentHeadsUp.key == postedEntry.key) { // Update the child's posted update so that it postedEntry.shouldHeadsUpEver = true postedEntry.shouldHeadsUpAgain = true handlePostedEntry( postedEntry, hunMutator, - scenario = "child-alert-transfer-target-$targetType") - didAlertChildToReceiveParentAlert = true + scenario = "child-heads-up-transfer-target-$targetType") + didHeadsUpChildToReceiveParentHeadsUp = true } else { handlePostedEntry( postedEntry, hunMutator, - scenario = "child-alert-non-target") + scenario = "child-heads-up-non-target") } } - // If the child receiving the alert was not updated on this tick (which can happen in a - // standard alert transfer scenario), then construct an update so that we can apply it. - if (!didAlertChildToReceiveParentAlert) { + // If the child receiving the heads up notification was not updated on this tick + // (which can happen in a standard heads up transfer scenario), then construct an update + // so that we can apply it. + if (!didHeadsUpChildToReceiveParentHeadsUp) { val posted = PostedEntry( - childToReceiveParentAlert, + childToReceiveParentHeadsUp, wasAdded = false, wasUpdated = false, shouldHeadsUpEver = true, shouldHeadsUpAgain = true, - isAlerting = mHeadsUpManager.isHeadsUpEntry(childToReceiveParentAlert.key), - isBinding = isEntryBinding(childToReceiveParentAlert), + isHeadsUpEntry = + mHeadsUpManager.isHeadsUpEntry(childToReceiveParentHeadsUp.key), + isBinding = isEntryBinding(childToReceiveParentHeadsUp), ) handlePostedEntry( posted, hunMutator, - scenario = "non-posted-child-alert-transfer-target-$targetType") + scenario = "non-posted-child-heads-up-transfer-target-$targetType") } } // After this method runs, all posted entries should have been handled (or skipped). @@ -286,9 +289,9 @@ class HeadsUpCoordinator @Inject constructor( /** * Find the posted child with the newest when, and return it if it is isolated and has - * GROUP_ALERT_SUMMARY so that it can be alerted. + * GROUP_ALERT_SUMMARY so that it can be heads uped. */ - private fun findAlertOverride( + private fun findHeadsUpOverride( postedEntries: List<PostedEntry>, locationLookupByKey: (String) -> GroupLocation, ): NotificationEntry? = postedEntries.asSequence() @@ -344,16 +347,17 @@ class HeadsUpCoordinator @Inject constructor( } } else { if (posted.isHeadsUpAlready) { - // NOTE: This might be because we're alerting (i.e. tracked by HeadsUpManager) OR - // it could be because we're binding, and that will affect the next step. + // NOTE: This might be because we're showing heads up (i.e. tracked by + // HeadsUpManager) OR it could be because we're binding, and that will affect the + // next step. if (posted.shouldHeadsUpEver) { - // If alerting, we need to post an update. Otherwise we're still binding, - // and we can just let that finish. - if (posted.isAlerting) { + // If showing heads up, we need to post an update. Otherwise we're still + // binding, and we can just let that finish. + if (posted.isHeadsUpEntry) { hunMutator.updateNotification(posted.key, posted.shouldHeadsUpAgain) } } else { - if (posted.isAlerting) { + if (posted.isHeadsUpEntry) { // We don't want this to be interrupting anymore, let's remove it hunMutator.removeNotification(posted.key, false /*removeImmediately*/) } else { @@ -408,7 +412,7 @@ class HeadsUpCoordinator @Inject constructor( wasUpdated = false, shouldHeadsUpEver = shouldHeadsUpEver, shouldHeadsUpAgain = true, - isAlerting = false, + isHeadsUpEntry = false, isBinding = false, ) @@ -418,21 +422,21 @@ class HeadsUpCoordinator @Inject constructor( /** * Notification could've updated to be heads up or not heads up. Even if it did update to - * heads up, if the notification specified that it only wants to alert once, don't heads + * heads up, if the notification specified that it only wants to heads up once, don't heads * up again. */ override fun onEntryUpdated(entry: NotificationEntry) { val shouldHeadsUpEver = mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt val shouldHeadsUpAgain = shouldHunAgain(entry) - val isAlerting = mHeadsUpManager.isHeadsUpEntry(entry.key) + val isHeadsUpEntry = mHeadsUpManager.isHeadsUpEntry(entry.key) val isBinding = isEntryBinding(entry) val posted = mPostedEntries.compute(entry.key) { _, value -> value?.also { update -> update.wasUpdated = true update.shouldHeadsUpEver = shouldHeadsUpEver update.shouldHeadsUpAgain = update.shouldHeadsUpAgain || shouldHeadsUpAgain - update.isAlerting = isAlerting + update.isHeadsUpEntry = isHeadsUpEntry update.isBinding = isBinding } ?: PostedEntry( entry, @@ -440,15 +444,15 @@ class HeadsUpCoordinator @Inject constructor( wasUpdated = true, shouldHeadsUpEver = shouldHeadsUpEver, shouldHeadsUpAgain = shouldHeadsUpAgain, - isAlerting = isAlerting, + isHeadsUpEntry = isHeadsUpEntry, isBinding = isBinding, ) } - // Handle cancelling alerts here, rather than in the OnBeforeFinalizeFilter, so that + // Handle cancelling heads up here, rather than in the OnBeforeFinalizeFilter, so that // work can be done before the ShadeListBuilder is run. This prevents re-entrant // behavior between this Coordinator, HeadsUpManager, and VisualStabilityManager. if (posted?.shouldHeadsUpEver == false) { - if (posted.isAlerting) { + if (posted.isHeadsUpEntry) { // We don't want this to be interrupting anymore, let's remove it mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/) } else if (posted.isBinding) { @@ -462,7 +466,7 @@ class HeadsUpCoordinator @Inject constructor( } /** - * Stop alerting HUNs that are removed from the notification collection + * Stop showing as heads up once removed from the notification collection */ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { mPostedEntries.remove(entry.key) @@ -484,7 +488,7 @@ class HeadsUpCoordinator @Inject constructor( /** * Identify notifications whose heads-up state changes when the notification rankings are - * updated, and have those changed notifications alert if necessary. + * updated, and have those changed notifications heads up if necessary. * * This method will occur after any operations in onEntryAdded or onEntryUpdated, so any * handling of ranking changes needs to take into account that we may have just made a @@ -492,7 +496,7 @@ class HeadsUpCoordinator @Inject constructor( */ override fun onRankingApplied() { // Because a ranking update may cause some notifications that are no longer (or were - // never) in mPostedEntries to need to alert, we need to check every notification + // never) in mPostedEntries to need to heads up, we need to check every notification // known to the pipeline. for (entry in mNotifPipeline.allNotifs) { // Only consider entries that are recent enough, since we want to apply a fairly @@ -500,9 +504,9 @@ class HeadsUpCoordinator @Inject constructor( // app-provided notification update. if (!isNewEnoughForRankingUpdate(entry)) continue - // The only entries we consider alerting for here are entries that have never - // interrupted and that now say they should heads up or FSI; if they've alerted in - // the past, we don't want to incorrectly alert a second time if there wasn't an + // The only entries we consider heads up for here are entries that have never + // interrupted and that now say they should heads up or FSI; if they've heads uped in + // the past, we don't want to incorrectly heads up a second time if there wasn't an // explicit notification update. if (entry.hasInterrupted()) continue @@ -561,7 +565,7 @@ class HeadsUpCoordinator @Inject constructor( } /** - * Checks whether an update for a notification warrants an alert for the user. + * Checks whether an update for a notification warrants an heads up for the user. */ private fun shouldHunAgain(entry: NotificationEntry): Boolean { return (!entry.hasInterrupted() || @@ -716,25 +720,25 @@ class HeadsUpCoordinator @Inject constructor( } /** - * Whether the notification is already alerting or binding so that it can imminently alert + * Whether the notification is already heads up or binding so that it can imminently heads up */ private fun isAttemptingToShowHun(entry: ListEntry) = mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry) /** - * Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it - * has been updated so that it should alert this update. This method is permissive because it - * returns `true` even if the update would (in isolation of its group) cause the alert to be - * retracted. This is important for not retracting transferred group alerts. + * Whether the notification is already heads up/binding per [isAttemptingToShowHun] OR if it + * has been updated so that it should heads up this update. This method is permissive because + * it returns `true` even if the update would (in isolation of its group) cause the heads up to + * be retracted. This is important for not retracting transferred group heads ups. */ private fun isGoingToShowHunNoRetract(entry: ListEntry) = mPostedEntries[entry.key]?.calculateShouldBeHeadsUpNoRetract ?: isAttemptingToShowHun(entry) /** * If the notification has been updated, then whether it should HUN in isolation, otherwise - * defers to the already alerting/binding state of [isAttemptingToShowHun]. This method is - * strict because any update which would revoke the alert supersedes the current - * alerting/binding state. + * defers to the already heads up/binding state of [isAttemptingToShowHun]. This method is + * strict because any update which would revoke the heads up supersedes the current + * heads up/binding state. */ private fun isGoingToShowHunStrict(entry: ListEntry) = mPostedEntries[entry.key]?.calculateShouldBeHeadsUpStrict ?: isAttemptingToShowHun(entry) @@ -760,12 +764,12 @@ class HeadsUpCoordinator @Inject constructor( var wasUpdated: Boolean, var shouldHeadsUpEver: Boolean, var shouldHeadsUpAgain: Boolean, - var isAlerting: Boolean, + var isHeadsUpEntry: Boolean, var isBinding: Boolean, ) { val key = entry.key val isHeadsUpAlready: Boolean - get() = isAlerting || isBinding + get() = isHeadsUpEntry || isBinding val calculateShouldBeHeadsUpStrict: Boolean get() = shouldHeadsUpEver && (wasAdded || shouldHeadsUpAgain || isHeadsUpAlready) val calculateShouldBeHeadsUpNoRetract: Boolean @@ -781,7 +785,7 @@ private fun Map<String, GroupLocation>.getLocation(key: String): GroupLocation = /** * Invokes the given block with a [HunMutator] that defers all HUN removals. This ensures that the * HeadsUpManager is notified of additions before removals, which prevents a glitch where the - * HeadsUpManager temporarily believes that nothing is alerting, causing bad re-entrant behavior. + * HeadsUpManager temporarily believes that nothing is heads up, causing bad re-entrant behavior. */ private fun <R> HeadsUpManager.modifyHuns(block: (HunMutator) -> R): R { val mutator = HunMutatorImpl(this) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 20fae88b6f33..c90aceef6934 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -263,8 +263,8 @@ public class AmbientState implements Dumpable { return mStackHeight; } - /** Tracks the state from AlertingNotificationManager#hasNotifications() */ - private boolean mHasAlertEntries; + /** Tracks the state from HeadsUpManager#hasNotifications() */ + private boolean mHasHeadsUpEntries; @Inject public AmbientState( @@ -563,7 +563,7 @@ public class AmbientState implements Dumpable { } public boolean hasPulsingNotifications() { - return mPulsing && mHasAlertEntries; + return mPulsing && mHasHeadsUpEntries; } public void setPulsing(boolean hasPulsing) { @@ -716,8 +716,8 @@ public class AmbientState implements Dumpable { return mAppearFraction; } - public void setHasAlertEntries(boolean hasAlertEntries) { - mHasAlertEntries = hasAlertEntries; + public void setHasHeadsUpEntries(boolean hasHeadsUpEntries) { + mHasHeadsUpEntries = hasHeadsUpEntries; } public void setStackTopMargin(int stackTopMargin) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index aa9d3b23e47b..933a78009e29 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -5721,7 +5721,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable void setNumHeadsUp(long numHeadsUp) { mNumHeadsUp = numHeadsUp; - mAmbientState.setHasAlertEntries(numHeadsUp > 0); + mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0); } public boolean getIsExpanded() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index ff00cb31783d..476b05492758 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -25,31 +25,37 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING -import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN -import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters +import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GoneToDozingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor @@ -85,67 +91,32 @@ constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, private val shadeInteractor: ShadeInteractor, communalInteractor: CommunalInteractor, + private val alternateBouncerToGoneTransitionViewModel: + AlternateBouncerToGoneTransitionViewModel, + private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, + private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, + private val glanceableHubToLockscreenTransitionViewModel: + GlanceableHubToLockscreenTransitionViewModel, + private val goneToDozingTransitionViewModel: GoneToDozingTransitionViewModel, + private val goneToDreamingTransitionViewModel: GoneToDreamingTransitionViewModel, + private val lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel, + private val lockscreenToGlanceableHubTransitionViewModel: + LockscreenToGlanceableHubTransitionViewModel, + private val lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, + private val lockscreenToPrimaryBouncerTransitionViewModel: + LockscreenToPrimaryBouncerTransitionViewModel, + private val lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel, + private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel, private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, - lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel, - alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, - primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, - lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel, - dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, - lockscreenToDreamingTransitionViewModel: LockscreenToDreamingTransitionViewModel, - glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, - lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, + private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel, + private val primaryBouncerToLockscreenTransitionViewModel: + PrimaryBouncerToLockscreenTransitionViewModel, private val aodBurnInViewModel: AodBurnInViewModel, ) { private val statesForConstrainedNotifications: Set<KeyguardState> = setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER) - private val edgeToAlphaViewModel = - mapOf<Edge?, (ViewStateAccessor) -> Flow<Float>>( - Edge(from = LOCKSCREEN, to = DREAMING) to - { _: ViewStateAccessor -> - lockscreenToDreamingTransitionViewModel.lockscreenAlpha - }, - Edge(from = LOCKSCREEN, to = GONE) to - { viewState: ViewStateAccessor -> - lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState) - }, - Edge(from = ALTERNATE_BOUNCER, to = GONE) to - { _: ViewStateAccessor -> - alternateBouncerToGoneTransitionViewModel.lockscreenAlpha - }, - Edge(from = PRIMARY_BOUNCER, to = GONE) to - { _: ViewStateAccessor -> - primaryBouncerToGoneTransitionViewModel.lockscreenAlpha - }, - Edge(from = DREAMING, to = LOCKSCREEN) to - { _: ViewStateAccessor -> - dreamingToLockscreenTransitionViewModel.lockscreenAlpha - }, - Edge(from = LOCKSCREEN, to = OCCLUDED) to - { _: ViewStateAccessor -> - lockscreenToOccludedTransitionViewModel.lockscreenAlpha - }, - Edge(from = OCCLUDED, to = LOCKSCREEN) to - { _: ViewStateAccessor -> - occludedToLockscreenTransitionViewModel.lockscreenAlpha - }, - ) - - private val lockscreenTransitionInProgress: Flow<Edge?> = - keyguardTransitionInteractor.transitions - .map { step -> - if ( - (step.transitionState == STARTED || step.transitionState == RUNNING) && - (step.from == LOCKSCREEN || step.to == LOCKSCREEN) - ) { - Edge(step.from, step.to) - } else { - null - } - } - .distinctUntilChanged() - .onStart { emit(null) } - private val lockscreenToGlanceableHubRunning = keyguardTransitionInteractor .transition(LOCKSCREEN, GLANCEABLE_HUB) @@ -300,54 +271,79 @@ constructor( private val alphaForShadeAndQsExpansion: Flow<Float> = interactor.configurationBasedDimensions .flatMapLatest { configurationBasedDimensions -> - combine( + combineTransform( shadeInteractor.shadeExpansion, shadeInteractor.qsExpansion, ) { shadeExpansion, qsExpansion -> if (shadeExpansion > 0f || qsExpansion > 0f) { if (configurationBasedDimensions.useSplitShade) { - 1f + emit(1f) } else { // Fade as QS shade expands - 1f - qsExpansion + emit(1f - qsExpansion) } - } else { - // Not visible unless the shade/qs is visible - 0f } } } - .distinctUntilChanged() + .onStart { emit(0f) } + + private val alphaWhenGoneAndShadeState: Flow<Float> = + combineTransform( + keyguardTransitionInteractor.transitions + .map { step -> step.to == GONE && step.transitionState == FINISHED } + .distinctUntilChanged(), + keyguardInteractor.statusBarState, + ) { isGoneTransitionFinished, statusBarState -> + if (isGoneTransitionFinished && statusBarState == SHADE) { + emit(1f) + } + } fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> { - // Due to issues with the legacy shade, some shade expansion events are sent incorrectly, - // such as when the shade resets. This can happen while the transition to/from LOCKSCREEN - // is running. Therefore use a series of flatmaps to prevent unwanted interruptions while - // those transitions are in progress. Without this, the alpha value will produce a visible - // flicker. - return lockscreenTransitionInProgress - .flatMapLatest { edge -> - edgeToAlphaViewModel.getOrDefault( - edge, - { _: ViewStateAccessor -> - isOnLockscreenWithoutShade.flatMapLatest { isOnLockscreenWithoutShade -> - combineTransform( - keyguardInteractor.keyguardAlpha, - shadeCollpaseFadeIn, - alphaForShadeAndQsExpansion, - ) { alpha, shadeCollpaseFadeIn, alphaForShadeAndQsExpansion -> - if (isOnLockscreenWithoutShade) { - if (!shadeCollpaseFadeIn) { - emit(alpha) - } - } else { - emit(alphaForShadeAndQsExpansion) - } - } + // All transition view models are mututally exclusive, and safe to merge + val alphaTransitions = + merge( + alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, + aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), + dozingToLockscreenTransitionViewModel.lockscreenAlpha, + dreamingToLockscreenTransitionViewModel.lockscreenAlpha, + goneToDreamingTransitionViewModel.lockscreenAlpha, + goneToDozingTransitionViewModel.lockscreenAlpha, + lockscreenToDreamingTransitionViewModel.lockscreenAlpha, + lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState), + lockscreenToOccludedTransitionViewModel.lockscreenAlpha, + lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha, + occludedToAodTransitionViewModel.lockscreenAlpha, + occludedToLockscreenTransitionViewModel.lockscreenAlpha, + primaryBouncerToGoneTransitionViewModel.lockscreenAlpha, + primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha, + ) + + return merge( + alphaTransitions, + // Sends a final alpha value of 1f when truly gone, to make sure HUNs appear + alphaWhenGoneAndShadeState, + // These remaining cases handle alpha changes within an existing state, such as + // shade expansion or swipe to dismiss + combineTransform( + isOnLockscreenWithoutShade, + shadeCollpaseFadeIn, + alphaForShadeAndQsExpansion, + keyguardInteractor.dismissAlpha, + ) { + isOnLockscreenWithoutShade, + shadeCollpaseFadeIn, + alphaForShadeAndQsExpansion, + dismissAlpha -> + if (isOnLockscreenWithoutShade) { + if (!shadeCollpaseFadeIn && dismissAlpha != null) { + emit(dismissAlpha) } + } else { + emit(alphaForShadeAndQsExpansion) } - )(viewState) - } + }, + ) .distinctUntilChanged() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index f9702dd12535..a39bfe00be28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -81,6 +81,7 @@ private constructor( statusContainer.setOnHoverListener( statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer) ) + statusContainer.setOnClickListener { shadeViewController.expand(/* animate= */true) } progressProvider?.setReadyToHandleTransition(true) configurationController.addCallback(configurationListener) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt index a7352be8d80a..420701f026d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt @@ -61,16 +61,16 @@ interface HeadsUpManager : Dumpable { fun getTouchableRegion(): Region? /** - * Whether or not there are any active alerting notifications. + * Whether or not there are any entries managed by HeadsUpManager. * - * @return true if there is an alert, false otherwise + * @return true if there is a heads up entry, false otherwise */ fun hasNotifications(): Boolean = false /** Returns whether there are any pinned Heads Up Notifications or not. */ fun hasPinnedHeadsUp(): Boolean - /** Returns whether or not the given notification is alerting and managed by this manager. */ + /** Returns whether or not the given notification is managed by this manager. */ fun isHeadsUpEntry(key: String): Boolean fun isHeadsUpGoingAway(): Boolean diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index eb08f37503c6..eba3162febe5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -16,9 +16,12 @@ package com.android.systemui.statusbar.policy; +import android.annotation.FlaggedApi; import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.hardware.SensorPrivacyManager.Sources.Source; +import com.android.internal.camera.flags.Flags; + public interface IndividualSensorPrivacyController extends CallbackController<IndividualSensorPrivacyController.Callback> { void init(); @@ -42,6 +45,12 @@ public interface IndividualSensorPrivacyController extends */ boolean requiresAuthentication(); + /** + * @return whether camera privacy is enabled for the package. + */ + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + boolean isCameraPrivacyEnabled(String packageName); + interface Callback { void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 87dfc9962675..58b82f166623 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -19,6 +19,9 @@ package com.android.systemui.statusbar.policy; import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; +import android.Manifest; +import android.annotation.FlaggedApi; +import android.annotation.RequiresPermission; import android.hardware.SensorPrivacyManager; import android.hardware.SensorPrivacyManager.Sensors.Sensor; import android.hardware.SensorPrivacyManager.Sources.Source; @@ -28,6 +31,8 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; +import com.android.internal.camera.flags.Flags; + import java.util.Set; public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController { @@ -102,6 +107,13 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr } @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean isCameraPrivacyEnabled(String packageName) { + return mSensorPrivacyManager.isCameraPrivacyEnabled(packageName); + } + + @Override public void addCallback(@NonNull Callback listener) { mCallbacks.add(listener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 43f7c60721ee..2c1a87d86be9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -20,6 +20,8 @@ import android.content.pm.PackageManager import android.hardware.biometrics.BiometricAuthenticator import android.hardware.biometrics.BiometricConstants import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT +import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.biometrics.PromptInfo import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorPropertiesInternal @@ -38,11 +40,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils -import com.android.systemui.res.R +import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository -import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor @@ -53,6 +55,7 @@ import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.events.ANIMATING_OUT import com.android.systemui.user.domain.interactor.SelectedUserInteractor @@ -145,6 +148,8 @@ open class AuthContainerViewTest : SysuiTestCase() { @Before fun setup() { + mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP) displayRepository = FakeDisplayRepository() displayStateInteractor = @@ -394,6 +399,19 @@ open class AuthContainerViewTest : SysuiTestCase() { } @Test + fun testShowBiometricUIWhenCustomBpEnabledAndNoSensors() { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + val container = initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) + waitForIdleSync() + + assertThat(customBiometricPrompt()).isTrue() + assertThat(container.hasBiometricPrompt()).isTrue() + assertThat(container.hasCredentialView()).isFalse() + } + + @Test fun testCredentialViewUsesEffectiveUserId() { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200) whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt index f3807e4a1391..c381749ec6d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt @@ -59,6 +59,24 @@ class AodToLockscreenTransitionViewModelTest : SysuiTestCase() { } @Test + fun lockscreenAlphaStartsFromViewStateAccessorAlpha() = + testScope.runTest { + val viewState = ViewStateAccessor(alpha = { 0.5f }) + val alpha by collectLastValue(underTest.lockscreenAlpha(viewState)) + + repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + + repository.sendTransitionStep(step(0f)) + assertThat(alpha).isEqualTo(0.5f) + + repository.sendTransitionStep(step(0.5f)) + assertThat(alpha).isEqualTo(0.75f) + + repository.sendTransitionStep(step(1f)) + assertThat(alpha).isEqualTo(1f) + } + + @Test fun deviceEntryBackgroundView_udfps_alphaFadeIn() = testScope.runTest { fingerprintPropertyRepository.supportsUdfps() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 17e4e0fb33cb..61fee16f0431 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -186,6 +186,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); + KeyguardTransitionInteractor keyguardTransitionInteractor = + mKosmos.getKeyguardTransitionInteractor(); KeyguardInteractor keyguardInteractor = new KeyguardInteractor( keyguardRepository, new FakeCommandQueue(), @@ -194,12 +196,10 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(configurationRepository), shadeRepository, + keyguardTransitionInteractor, () -> sceneInteractor); CommunalInteractor communalInteractor = mKosmos.getCommunalInteractor(); - KeyguardTransitionInteractor keyguardTransitionInteractor = - mKosmos.getKeyguardTransitionInteractor(); - mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = mKosmos.getFromPrimaryBouncerTransitionInteractor(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index 2f765d540b6b..061f88e8a592 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -213,6 +213,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { mKosmos.getDeviceUnlockedInteractor()); FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); + KeyguardTransitionInteractor keyguardTransitionInteractor = + mKosmos.getKeyguardTransitionInteractor(); KeyguardInteractor keyguardInteractor = new KeyguardInteractor( mKeyguardRepository, new FakeCommandQueue(), @@ -221,11 +223,9 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(configurationRepository), mShadeRepository, + keyguardTransitionInteractor, () -> sceneInteractor); - KeyguardTransitionInteractor keyguardTransitionInteractor = - mKosmos.getKeyguardTransitionInteractor(); - mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = mKosmos.getFromPrimaryBouncerTransitionInteractor(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt index 64fd80d72d3f..74d017375eb5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.plugins.PluginListener import com.android.systemui.plugins.PluginManager import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq +import com.android.systemui.util.ThreadAssert import java.util.function.BiConsumer import junit.framework.Assert.assertEquals import junit.framework.Assert.fail @@ -69,6 +70,7 @@ class ClockRegistryTest : SysuiTestCase() { @Mock private lateinit var mockDefaultClock: ClockController @Mock private lateinit var mockThumbnail: Drawable @Mock private lateinit var mockContentResolver: ContentResolver + @Mock private lateinit var mockThreadAssert: ThreadAssert private lateinit var fakeDefaultProvider: FakeClockPlugin private lateinit var pluginListener: PluginListener<ClockProviderPlugin> private lateinit var registry: ClockRegistry @@ -163,14 +165,12 @@ class ClockRegistryTest : SysuiTestCase() { defaultClockProvider = fakeDefaultProvider, keepAllLoaded = false, subTag = "Test", + assert = mockThreadAssert, ) { override fun querySettings() { } override fun applySettings(value: ClockSettings?) { settings = value } - // Unit Test does not validate threading - override fun assertMainThread() {} - override fun assertNotMainThread() {} } registry.registerListeners() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index fb105e2ef759..1396a430df61 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -122,6 +122,10 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { val shadeRepository = FakeShadeRepository() val sceneContainerFlags = FakeSceneContainerFlags() val configurationRepository = FakeConfigurationRepository() + val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor + fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor + fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor + val keyguardInteractor = KeyguardInteractor( keyguardRepository, @@ -131,11 +135,9 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { FakeKeyguardBouncerRepository(), ConfigurationInteractor(configurationRepository), shadeRepository, + keyguardTransitionInteractor, { kosmos.sceneInteractor }, ) - val keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor - fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor - fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow()) shadeInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 9055ba4e1c4e..2da88e9f0ef9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -47,6 +47,8 @@ import com.android.systemui.res.R import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.mockLargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor +import com.android.systemui.statusbar.policy.SplitShadeStateController +import com.android.systemui.statusbar.policy.splitShadeStateController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever @@ -65,6 +67,7 @@ import org.mockito.Mockito.mock @RunWith(AndroidJUnit4::class) class SharedNotificationContainerViewModelTest : SysuiTestCase() { val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) + val splitShadeStateController = mock(SplitShadeStateController::class.java) lateinit var translationYFlow: MutableStateFlow<Float> val kosmos = @@ -77,6 +80,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { init { kosmos.aodBurnInViewModel = aodBurnInViewModel + kosmos.splitShadeStateController = splitShadeStateController } val testScope = kosmos.testScope val configurationRepository = kosmos.fakeConfigurationRepository @@ -93,7 +97,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Before fun setUp() { - overrideResource(R.bool.config_use_split_notification_shade, false) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())).thenReturn(false) translationYFlow = MutableStateFlow(0f) whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow) underTest = kosmos.sharedNotificationContainerViewModel @@ -102,7 +106,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginStartInSplitShade() = testScope.runTest { - overrideResource(R.bool.config_use_split_notification_shade, true) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(true) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -115,7 +120,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validateMarginStart() = testScope.runTest { - overrideResource(R.bool.config_use_split_notification_shade, false) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(false) overrideResource(R.dimen.notification_panel_margin_horizontal, 20) val dimens by collectLastValue(underTest.configurationBasedDimensions) @@ -130,7 +136,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { testScope.runTest { mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - overrideResource(R.bool.config_use_split_notification_shade, true) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(true) + overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -147,12 +155,13 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { testScope.runTest { mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - overrideResource(R.bool.config_use_split_notification_shade, true) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(true) + overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) val dimens by collectLastValue(underTest.configurationBasedDimensions) - configurationRepository.onAnyConfigurationChange() // Should directly use the header height (flagged on value) @@ -162,7 +171,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Test fun validatePaddingTop() = testScope.runTest { - overrideResource(R.bool.config_use_split_notification_shade, false) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(false) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -421,7 +431,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val bounds by collectLastValue(underTest.bounds) // When not in split shade - overrideResource(R.bool.config_use_split_notification_shade, false) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(false) configurationRepository.onAnyConfigurationChange() runCurrent() @@ -443,7 +454,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When in split shade whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - overrideResource(R.bool.config_use_split_notification_shade, true) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(true) + overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -470,7 +483,9 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // When in split shade whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(5) - overrideResource(R.bool.config_use_split_notification_shade, true) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(true) + overrideResource(R.bool.config_use_large_screen_shade_header, true) overrideResource(R.dimen.large_screen_shade_header_height, 10) overrideResource(R.dimen.keyguard_split_shade_top_margin, 50) @@ -528,7 +543,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() - overrideResource(R.bool.config_use_split_notification_shade, false) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(false) configurationRepository.onAnyConfigurationChange() keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = 1f, bottom = 2f) @@ -551,7 +567,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() - overrideResource(R.bool.config_use_split_notification_shade, false) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(false) configurationRepository.onAnyConfigurationChange() keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = 1f, bottom = 2f) @@ -587,7 +604,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { // Show lockscreen with shade expanded showLockscreenWithShadeExpanded() - overrideResource(R.bool.config_use_split_notification_shade, false) + whenever(splitShadeStateController.shouldUseSplitNotificationShade(any())) + .thenReturn(false) configurationRepository.onAnyConfigurationChange() keyguardInteractor.setNotificationContainerBounds( NotificationContainerBounds(top = 1f, bottom = 2f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 76c9740f77dc..56fc7b9d818f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -60,6 +60,7 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; @@ -162,7 +163,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager); - + KeyguardTransitionInteractor keyguardTransitionInteractor = + mKosmos.getKeyguardTransitionInteractor(); mKeyguardInteractor = new KeyguardInteractor( mKeyguardRepository, mCommandQueue, @@ -171,6 +173,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(new FakeConfigurationRepository()), new FakeShadeRepository(), + keyguardTransitionInteractor, () -> mKosmos.getSceneInteractor()); mViewModel = new KeyguardStatusBarViewModel( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index b7560ad79026..1687ccbf5826 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -22,15 +22,16 @@ import android.app.StatusBarManager.WINDOW_STATE_SHOWING import android.app.StatusBarManager.WINDOW_STATUS_BAR import android.view.LayoutInflater import android.view.MotionEvent +import android.view.View import android.view.ViewTreeObserver import android.view.ViewTreeObserver.OnPreDrawListener import android.widget.FrameLayout import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.ui.view.WindowRootView import com.android.systemui.shade.ShadeControllerImpl @@ -48,8 +49,6 @@ import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat -import java.util.Optional -import javax.inject.Provider import org.junit.Before import org.junit.Test import org.mockito.ArgumentCaptor @@ -60,6 +59,8 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import java.util.Optional +import javax.inject.Provider @SmallTest class PhoneStatusBarViewControllerTest : SysuiTestCase() { @@ -98,7 +99,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { val parent = FrameLayout(mContext) // add parent to keep layout params view = LayoutInflater.from(mContext).inflate(R.layout.status_bar, parent, false) - as PhoneStatusBarView + as PhoneStatusBarView controller = createAndInitController(view) } } @@ -231,6 +232,27 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { verify(centralSurfacesImpl).setInteracting(any(), any()) } + @Test + fun shadeIsExpandedOnStatusIconClick() { + val view = createViewMock() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } + val statusContainer = view.requireViewById<View>(R.id.system_icons) + statusContainer.performClick() + verify(shadeViewController).expand(any()) + } + + @Test + fun shadeIsNotExpandedOnStatusBarGeneralClick() { + val view = createViewMock() + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } + view.performClick() + verify(shadeViewController, never()).expand(any()) + } + private fun getCommandQueueCallback(): CommandQueue.Callbacks { val captor = argumentCaptor<CommandQueue.Callbacks>() verify(commandQueue).addCallback(captor.capture()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index ca0e526bbc30..76913e8e15ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractorFactory @@ -61,6 +62,7 @@ class KeyguardStatusBarViewModelTest : SysuiTestCase() { FakeKeyguardBouncerRepository(), ConfigurationInteractor(FakeConfigurationRepository()), FakeShadeRepository(), + kosmos.keyguardTransitionInteractor, ) { kosmos.sceneInteractor } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 97bd96d605ef..d87df0af6ba9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -420,6 +420,8 @@ public class BubblesTest extends SysuiTestCase { mKosmos.getDeviceUnlockedInteractor()); FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags(); + KeyguardTransitionInteractor keyguardTransitionInteractor = + mKosmos.getKeyguardTransitionInteractor(); KeyguardInteractor keyguardInteractor = new KeyguardInteractor( keyguardRepository, new FakeCommandQueue(), @@ -428,11 +430,9 @@ public class BubblesTest extends SysuiTestCase { new FakeKeyguardBouncerRepository(), new ConfigurationInteractor(configurationRepository), shadeRepository, + keyguardTransitionInteractor, () -> sceneInteractor); - KeyguardTransitionInteractor keyguardTransitionInteractor = - mKosmos.getKeyguardTransitionInteractor(); - mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); mFromPrimaryBouncerTransitionInteractor = mKosmos.getFromPrimaryBouncerTransitionInteractor(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index 0bba36b172c0..3893a9b74b2a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -23,6 +23,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -30,6 +31,8 @@ import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.flow.MutableSharedFlow /** * Simply put, I got tired of adding a constructor argument and then having to tweak dozens of @@ -50,6 +53,11 @@ object KeyguardInteractorFactory { sceneInteractor: SceneInteractor = mock(), powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor, ): WithDependencies { + // Mock this until the class is replaced by kosmos + val keyguardTransitionInteractor: KeyguardTransitionInteractor = mock() + val currentKeyguardStateFlow = MutableSharedFlow<KeyguardState>() + whenever(keyguardTransitionInteractor.currentKeyguardState) + .thenReturn(currentKeyguardStateFlow) return WithDependencies( repository = repository, commandQueue = commandQueue, @@ -67,6 +75,7 @@ object KeyguardInteractorFactory { configurationInteractor = ConfigurationInteractor(configurationRepository), shadeRepository = shadeRepository, sceneInteractorProvider = { sceneInteractor }, + keyguardTransitionInteractor = keyguardTransitionInteractor, powerInteractor = powerInteractor, ), ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt index 58d99b5bcc12..5140a9f5c2ba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt @@ -36,6 +36,7 @@ val Kosmos.keyguardInteractor by bouncerRepository = keyguardBouncerRepository, configurationInteractor = configurationInteractor, shadeRepository = shadeRepository, + keyguardTransitionInteractor = keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt index 0c38fd9c37a0..6df7493be200 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt @@ -26,7 +26,6 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by KeyguardTransitionInteractor( scope = applicationCoroutineScope, repository = keyguardTransitionRepository, - keyguardInteractor = Lazy { keyguardInteractor }, fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor }, fromPrimaryBouncerTransitionInteractor = Lazy { fromPrimaryBouncerTransitionInteractor }, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt index 6b89e0f8901a..9fb32841d201 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -26,8 +25,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.aodAlphaViewModel by Fixture { AodAlphaViewModel( - keyguardInteractor = keyguardInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, - occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, + goneToAodTransitionViewModel = goneToAodTransitionViewModel, + goneToDozingTransitionViewModel = goneToDozingTransitionViewModel, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..4daf46028979 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModelKosmos.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.goneToDozingTransitionViewModel by Fixture { + GoneToDozingTransitionViewModel( + animationFlow = keyguardTransitionAnimationFlow, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index 24bb9c5008bf..4939237bbafe 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -37,14 +37,24 @@ val Kosmos.keyguardRootViewModel by Fixture { communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, notificationsKeyguardInteractor = notificationsKeyguardInteractor, + alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, + glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, + lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel, + lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel, lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel, - alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, + lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel, + lockscreenToPrimaryBouncerTransitionViewModel = + lockscreenToPrimaryBouncerTransitionViewModel, + occludedToAodTransitionViewModel = occludedToAodTransitionViewModel, + occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, + primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel, primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel, + primaryBouncerToLockscreenTransitionViewModel = + primaryBouncerToLockscreenTransitionViewModel, screenOffAnimationController = screenOffAnimationController, aodBurnInViewModel = aodBurnInViewModel, aodAlphaViewModel = aodAlphaViewModel, - lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel, - glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 30d4105e8ca1..8882de06637c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -21,14 +21,21 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel +import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.goneToDozingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.goneToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.lockscreenToDreamingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.lockscreenToPrimaryBouncerTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.occludedToAodTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToLockscreenTransitionViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope @@ -43,15 +50,24 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { keyguardTransitionInteractor = keyguardTransitionInteractor, shadeInteractor = shadeInteractor, communalInteractor = communalInteractor, - occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, - lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, - primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel, - lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel, + aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, - lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel, + goneToDozingTransitionViewModel = goneToDozingTransitionViewModel, + goneToDreamingTransitionViewModel = goneToDreamingTransitionViewModel, glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, + lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel, lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel, + lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel, + lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel, + lockscreenToPrimaryBouncerTransitionViewModel = + lockscreenToPrimaryBouncerTransitionViewModel, + occludedToAodTransitionViewModel = occludedToAodTransitionViewModel, + occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel, + primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel, + primaryBouncerToLockscreenTransitionViewModel = + primaryBouncerToLockscreenTransitionViewModel, aodBurnInViewModel = aodBurnInViewModel, ) } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index 35ce4814f97d..132804f4f91d 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -24,6 +24,46 @@ java_library { visibility: ["//visibility:public"], } +java_library_host { + name: "ravenwood-helper-libcore-runtime.host", + srcs: [ + "runtime-helper-src/libcore-fake/**/*.java", + ], + visibility: ["//visibility:private"], +} + +java_host_for_device { + name: "ravenwood-helper-libcore-runtime", + libs: [ + "ravenwood-helper-libcore-runtime.host", + ], + visibility: ["//visibility:private"], +} + +java_library { + name: "ravenwood-helper-framework-runtime", + srcs: [ + "runtime-helper-src/framework/**/*.java", + ], + libs: [ + "framework-minus-apex.ravenwood", + ], + visibility: ["//visibility:private"], +} + +// Combine ravenwood-helper-*-runtime and create a single library, which we include +// in the ravenwood runtime. +// We do it this way rather than including the individual jars in the runtime, because +// for some reason we couldn't include a java_host_for_device module in the ravenwood runtime. +java_library { + name: "ravenwood-helper-runtime", + defaults: ["ravenwood-internal-only-visibility-java"], + static_libs: [ + "ravenwood-helper-framework-runtime", + "ravenwood-helper-libcore-runtime", + ], +} + java_library { name: "ravenwood-junit-impl", srcs: [ @@ -58,16 +98,6 @@ java_library { visibility: ["//visibility:public"], } -java_library { - // Prefixed with "200" to ensure it's sorted early in Tradefed classpath - // so that we provide a concrete implementation before Mainline stubs - name: "200-kxml2-android", - static_libs: [ - "kxml2-android", - ], - visibility: ["//frameworks/base"], -} - java_host_for_device { name: "androidx.test.monitor-for-device", libs: [ diff --git a/ravenwood/api-maintainers.md b/ravenwood/api-maintainers.md index d84cb6795fef..4b2f96804c97 100644 --- a/ravenwood/api-maintainers.md +++ b/ravenwood/api-maintainers.md @@ -82,7 +82,7 @@ When a pure-Java implementation grows too large or complex to host within the or ``` @RavenwoodKeepWholeClass -@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.MyComplexClass_host") +@RavenwoodNativeSubstitutionClass("com.android.platform.test.ravenwood.nativesubstitution.MyComplexClass_host") public class MyComplexClass { private static native void nativeDoThing(long nativePtr); ... diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/CursorWindow_host.java index eba99107f126..f38d5653d3a9 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/CursorWindow_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.database.Cursor; import android.database.sqlite.SQLiteException; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/EventLog_host.java index 6480cfc2b492..55d4ffb41e78 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/EventLog_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import com.android.internal.os.RuntimeInit; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Log_host.java index cdfa30276961..5930a14cdec8 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Log_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.util.Log; import android.util.Log.Level; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java index 4d39d88d58c3..741411095f53 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.os.BadParcelableException; import android.os.Parcel; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongMultiStateCounter_host.java index a5d0fc6872de..9486651ce48d 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongMultiStateCounter_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.os.BadParcelableException; import android.os.Parcel; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/MessageQueue_host.java index 65da4a144160..5e81124b6e70 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/MessageQueue_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/ParcelFileDescriptor_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java index 0ebaac6016e2..2d799142df70 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/ParcelFileDescriptor_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import static android.os.ParcelFileDescriptor.MODE_APPEND; import static android.os.ParcelFileDescriptor.MODE_CREATE; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java index d63bff6f4da3..81ad31e631fe 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java index 2f6a361e3609..eba6c8b2db64 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.nativesubstitution; +package com.android.platform.test.ravenwood.nativesubstitution; import android.util.SparseArray; diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index fbcc64892798..1e120305fa2d 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.hoststubgen.runtimehelper; - -import com.android.hoststubgen.hosthelper.HostTestException; +package com.android.platform.test.ravenwood.runtimehelper; import java.io.File; import java.io.PrintStream; @@ -79,7 +77,7 @@ public class ClassLoadHook { private static void ensurePropertyNotSet(String key) { if (System.getProperty(key) != null) { - throw new HostTestException("System property \"" + key + "\" is set unexpectedly"); + throw new RuntimeException("System property \"" + key + "\" is set unexpectedly"); } } diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java index 388156aa3694..388156aa3694 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java +++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/ErrnoException.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java index 379c4ae8a059..379c4ae8a059 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java +++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java index 7d2b00d9420d..7d2b00d9420d 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java +++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java index 65c285e06bf8..65c285e06bf8 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/io/IoUtils.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/EmptyArray.java index a1ae35a88656..a1ae35a88656 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/EmptyArray.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/HexEncoding.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/HexEncoding.java index cc2fb7bbf236..cc2fb7bbf236 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/HexEncoding.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/HexEncoding.java diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/SneakyThrow.java index e142c46bc311..e142c46bc311 100644 --- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/SneakyThrow.java diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index a341b4acaca1..2e14abbd4d40 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -263,6 +263,10 @@ public class SystemConfig { // location settings are off, for emergency purposes, as read from the configuration files. final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>(); + // These are the packages that are allow-listed to be able to access camera when + // the camera privacy state is for driver assistance apps only. + final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>(); + // These are the action strings of broadcasts which are whitelisted to // be delivered anonymously even to apps which target O+. final ArraySet<String> mAllowImplicitBroadcasts = new ArraySet<>(); @@ -483,6 +487,10 @@ public class SystemConfig { return mAllowedAssociations; } + public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() { + return mAllowlistCameraPrivacy; + } + public ArraySet<String> getBugreportWhitelistedPackages() { return mBugreportWhitelistedPackages; } @@ -1062,6 +1070,22 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; + case "camera-privacy-allowlisted-app" : { + if (allowOverrideAppRestrictions) { + String pkgname = parser.getAttributeValue(null, "package"); + boolean isMandatory = XmlUtils.readBooleanAttribute( + parser, "mandatory", false); + if (pkgname == null) { + Slog.w(TAG, "<" + name + "> without package in " + + permFile + " at " + parser.getPositionDescription()); + } else { + mAllowlistCameraPrivacy.put(pkgname, isMandatory); + } + } else { + logNotAllowedInPartition(name, permFile, parser); + } + XmlUtils.skipCurrentTag(parser); + } break; case "allow-ignore-location-settings": { if (allowOverrideAppRestrictions) { String pkgname = parser.getAttributeValue(null, "package"); diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index c85723525aa1..1dc384d61c91 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -383,6 +383,11 @@ public final class AppStartInfoTracker { start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid); start.setProcessName(app.processName); start.setPackageName(app.info.packageName); + if (android.content.pm.Flags.stayStopped()) { + // TODO: Verify this is created at the right time to have the correct force-stopped + // state in the ProcessRecord. Also use the WindowProcessRecord if activity. + start.setForceStopped(app.wasForceStopped()); + } } void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 5c95d433a6c1..145b213ad2c6 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -106,6 +106,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; +import android.hardware.SensorPrivacyManager; import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION; import android.net.Uri; import android.os.AsyncTask; @@ -151,6 +152,7 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; +import com.android.internal.camera.flags.Flags; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.Clock; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -223,6 +225,8 @@ public class AppOpsService extends IAppOpsService.Stub { */ private static final int CURRENT_VERSION = 1; + private SensorPrivacyManager mSensorPrivacyManager; + // Write at most every 30 minutes. static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000; @@ -1231,6 +1235,7 @@ public class AppOpsService extends IAppOpsService.Stub { } } }); + mSensorPrivacyManager = SensorPrivacyManager.getInstance(mContext); } @VisibleForTesting @@ -4642,6 +4647,10 @@ public class AppOpsService extends IAppOpsService.Stub { return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid)); } + private boolean isAutomotive() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE); + } + private boolean isOpRestrictedLocked(int uid, int code, String packageName, String attributionTag, int virtualDeviceId, @Nullable RestrictionBypass appBypass, boolean isCheckOp) { @@ -4658,6 +4667,13 @@ public class AppOpsService extends IAppOpsService.Stub { } } + if ((code == OP_CAMERA) && isAutomotive()) { + if ((Flags.cameraPrivacyAllowlist()) + && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) { + return true; + } + } + int userHandle = UserHandle.getUserId(uid); restrictionSetCount = mOpUserRestrictions.size(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 7fb3e001c4c3..9a76ebd148aa 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -18,6 +18,7 @@ package com.android.server.locksettings; import static android.security.Flags.reportPrimaryAuthAttempts; import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; +import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; import static android.Manifest.permission.SET_INITIAL_LOCK; @@ -27,6 +28,7 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRY import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE; import static android.content.Context.KEYGUARD_SERVICE; +import static android.content.Intent.ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_SYSTEM; @@ -1201,8 +1203,9 @@ public class LockSettingsService extends ILockSettings.Stub { final boolean inSetupWizard = Settings.Secure.getIntForUser(cr, Settings.Secure.USER_SETUP_COMPLETE, 0, mainUserId) == 0; - final boolean secureFrp = Settings.Global.getInt(cr, - Settings.Global.SECURE_FRP_MODE, 0) == 1; + final boolean secureFrp = android.security.Flags.frpEnforcement() + ? mStorage.isFactoryResetProtectionActive() + : (Settings.Global.getInt(cr, Settings.Global.SECURE_FRP_MODE, 0) == 1); if (inSetupWizard && secureFrp) { throw new SecurityException("Cannot change credential in SUW while factory reset" @@ -2332,8 +2335,13 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSpManager) { if (isSpecialUserId(userId)) { - return mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(), + response = mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(), credential, progressCallback); + if (android.security.Flags.frpEnforcement() && response.isMatched() + && userId == USER_FRP) { + mStorage.deactivateFactoryResetProtectionWithoutSecret(); + } + return response; } long protectorId = getCurrentLskfBasedProtectorId(userId); @@ -3054,6 +3062,7 @@ public class LockSettingsService extends ILockSettings.Stub { setCurrentLskfBasedProtectorId(newProtectorId, userId); LockPatternUtils.invalidateCredentialTypeCache(); synchronizeUnifiedChallengeForProfiles(userId, profilePasswords); + sendMainUserCredentialChangedNotificationIfNeeded(userId); setUserPasswordMetrics(credential, userId); mUnifiedProfilePasswordCache.removePassword(userId); @@ -3071,6 +3080,24 @@ public class LockSettingsService extends ILockSettings.Stub { return newProtectorId; } + private void sendMainUserCredentialChangedNotificationIfNeeded(int userId) { + if (!android.security.Flags.frpEnforcement()) { + return; + } + + if (userId != mInjector.getUserManagerInternal().getMainUserId()) { + return; + } + + sendBroadcast(new Intent(ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED), + UserHandle.of(userId), CONFIGURE_FACTORY_RESET_PROTECTION); + } + + @VisibleForTesting + void sendBroadcast(Intent intent, UserHandle userHandle, String permission) { + mContext.sendBroadcastAsUser(intent, userHandle, permission, /* options */ null); + } + private void removeBiometricsForUser(int userId) { removeAllFingerprintForUser(userId); removeAllFaceForUser(userId); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java index 6d123ccebc7c..158d444bcff2 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java @@ -34,6 +34,7 @@ import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.persistentdata.PersistentDataBlockManager; import android.text.TextUtils; import android.util.ArrayMap; import android.util.AtomicFile; @@ -587,6 +588,10 @@ class LockSettingsStorage { return mPersistentDataBlockManagerInternal; } + /** + * Writes main user credential handle to the persistent data block, to enable factory reset + * protection to be deactivated with the credential. + */ public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi, byte[] payload) { PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); @@ -610,6 +615,31 @@ class LockSettingsStorage { } } + public void deactivateFactoryResetProtectionWithoutSecret() { + PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager(); + if (persistentDataBlock != null) { + persistentDataBlock.deactivateFactoryResetProtectionWithoutSecret(); + } else { + Slog.wtf(TAG, "Failed to get PersistentDataBlockManagerInternal"); + } + } + + public boolean isFactoryResetProtectionActive() { + PersistentDataBlockManager persistentDataBlockManager = + mContext.getSystemService(PersistentDataBlockManager.class); + if (persistentDataBlockManager != null) { + return persistentDataBlockManager.isFactoryResetProtectionActive(); + } else { + Slog.wtf(TAG, "Failed to get PersistentDataBlockManager"); + // This should never happen, but in the event it does, let's not block the user. This + // may be the wrong call, since if an attacker can find a way to prevent us from + // getting the PersistentDataBlockManager they can defeat FRP, but if they can block + // access to PersistentDataBlockManager they must have compromised the system and we've + // probably already lost this battle. + return false; + } + } + /** * Provides a concrete data structure to represent the minimal information from * a user's LSKF-based SP protector that is needed to verify the user's LSKF, diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index bbb19e351b5d..1546895e8df9 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -550,8 +550,8 @@ public final class MediaProjectionManagerService extends SystemService break; case RECORD_CONTENT_TASK: IBinder taskWindowContainerToken = - mProjectionGrant.getLaunchCookie() == null ? null - : mProjectionGrant.getLaunchCookie().binder; + mProjectionGrant.getLaunchCookieInternal() == null ? null + : mProjectionGrant.getLaunchCookieInternal().binder; setReviewedConsentSessionLocked( ContentRecordingSession.createTaskSession(taskWindowContainerToken)); break; @@ -603,6 +603,17 @@ public final class MediaProjectionManagerService extends SystemService return projection; } + /** + * Test API mirroring the types in the aidl interface for access outside the projection + * package. + */ + @VisibleForTesting + public IMediaProjection createProjectionInternal(int processUid, String packageName, int type, + boolean isPermanentGrant) { + return createProjectionInternal(processUid, packageName, type, isPermanentGrant, + Binder.getCallingUserHandle()); + } + // TODO(b/261563516): Remove internal method and test aidl directly, here and elsewhere. @VisibleForTesting MediaProjection getProjectionInternal(int uid, String packageName) { @@ -1192,6 +1203,10 @@ public final class MediaProjectionManagerService extends SystemService @Override // Binder call public void setLaunchCookie(LaunchCookie launchCookie) { setLaunchCookie_enforcePermission(); + setLaunchCookieInternal(launchCookie); + } + + @VisibleForTesting void setLaunchCookieInternal(LaunchCookie launchCookie) { mLaunchCookie = launchCookie; } @@ -1199,6 +1214,10 @@ public final class MediaProjectionManagerService extends SystemService @Override // Binder call public LaunchCookie getLaunchCookie() { getLaunchCookie_enforcePermission(); + return getLaunchCookieInternal(); + } + + @VisibleForTesting LaunchCookie getLaunchCookieInternal() { return mLaunchCookie; } @@ -1206,6 +1225,11 @@ public final class MediaProjectionManagerService extends SystemService @Override public boolean isValid() { isValid_enforcePermission(); + return isValidInternal(); + } + + @VisibleForTesting + boolean isValidInternal() { synchronized (mLock) { final long curMs = mClock.uptimeMillis(); final boolean hasTimedOut = curMs - mCreateTimeMs > mTimeoutMs; @@ -1236,6 +1260,11 @@ public final class MediaProjectionManagerService extends SystemService @Override public void notifyVirtualDisplayCreated(int displayId) { notifyVirtualDisplayCreated_enforcePermission(); + notifyVirtualDisplayCreatedInternal(displayId); + } + + @VisibleForTesting + void notifyVirtualDisplayCreatedInternal(int displayId) { synchronized (mLock) { mVirtualDisplayId = displayId; diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index e349fa36368d..babb6c219714 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -22,6 +22,8 @@ import static android.app.Notification.FLAG_GROUP_SUMMARY; import static android.app.Notification.FLAG_LOCAL_ONLY; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; +import static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.Notification.VISIBILITY_PUBLIC; import android.annotation.NonNull; import android.app.Notification; @@ -145,7 +147,8 @@ public class GroupHelper { mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags, - sbn.getNotification().getSmallIcon(), sbn.getNotification().color); + sbn.getNotification().getSmallIcon(), sbn.getNotification().color, + sbn.getNotification().visibility); children.put(sbn.getKey(), attr); mUngroupedNotifications.put(key, children); @@ -158,25 +161,29 @@ public class GroupHelper { if (notificationsToGroup.size() > 0) { if (autogroupSummaryExists) { NotificationAttributes attr = new NotificationAttributes(flags, - sbn.getNotification().getSmallIcon(), sbn.getNotification().color); + sbn.getNotification().getSmallIcon(), sbn.getNotification().color, + VISIBILITY_PRIVATE); if (Flags.autogroupSummaryIconUpdate()) { - attr = updateAutobundledSummaryIcon(sbn.getPackageName(), childrenAttr, attr); + attr = updateAutobundledSummaryAttributes(sbn.getPackageName(), childrenAttr, + attr); } mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), attr); } else { Icon summaryIcon = sbn.getNotification().getSmallIcon(); int summaryIconColor = sbn.getNotification().color; + int summaryVisibility = VISIBILITY_PRIVATE; if (Flags.autogroupSummaryIconUpdate()) { - // Calculate the initial summary icon and icon color - NotificationAttributes iconAttr = getAutobundledSummaryIconAndColor( + // Calculate the initial summary icon, icon color and visibility + NotificationAttributes iconAttr = getAutobundledSummaryAttributes( sbn.getPackageName(), childrenAttr); summaryIcon = iconAttr.icon; summaryIconColor = iconAttr.iconColor; + summaryVisibility = iconAttr.visibility; } NotificationAttributes attr = new NotificationAttributes(flags, summaryIcon, - summaryIconColor); + summaryIconColor, summaryVisibility); mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), attr); } @@ -238,18 +245,19 @@ public class GroupHelper { mCallback.removeAutoGroupSummary(userId, sbn.getPackageName()); } else { NotificationAttributes attr = new NotificationAttributes(summaryFlags, - sbn.getNotification().getSmallIcon(), sbn.getNotification().color); - boolean iconUpdated = false; + sbn.getNotification().getSmallIcon(), sbn.getNotification().color, + VISIBILITY_PRIVATE); + boolean attributesUpdated = false; if (Flags.autogroupSummaryIconUpdate()) { - NotificationAttributes newAttr = updateAutobundledSummaryIcon(sbn.getPackageName(), - childrenAttrs, attr); + NotificationAttributes newAttr = updateAutobundledSummaryAttributes( + sbn.getPackageName(), childrenAttrs, attr); if (!newAttr.equals(attr)) { - iconUpdated = true; + attributesUpdated = true; attr = newAttr; } } - if (updateSummaryFlags || iconUpdated) { + if (updateSummaryFlags || attributesUpdated) { mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), attr); } } @@ -268,12 +276,13 @@ public class GroupHelper { } } - NotificationAttributes getAutobundledSummaryIconAndColor(@NonNull String packageName, + NotificationAttributes getAutobundledSummaryAttributes(@NonNull String packageName, @NonNull List<NotificationAttributes> childrenAttr) { Icon newIcon = null; boolean childrenHaveSameIcon = true; int newColor = Notification.COLOR_INVALID; boolean childrenHaveSameColor = true; + int newVisibility = VISIBILITY_PRIVATE; // Both the icon drawable and the icon background color are updated according to this rule: // - if all child icons are identical => use the common icon @@ -296,6 +305,10 @@ public class GroupHelper { childrenHaveSameColor = false; } } + // Check for visibility. If at least one child is public, then set to public + if (state.visibility == VISIBILITY_PUBLIC) { + newVisibility = VISIBILITY_PUBLIC; + } } if (!childrenHaveSameIcon) { newIcon = getMonochromeAppIcon(packageName); @@ -304,13 +317,13 @@ public class GroupHelper { newColor = COLOR_DEFAULT; } - return new NotificationAttributes(0, newIcon, newColor); + return new NotificationAttributes(0, newIcon, newColor, newVisibility); } - NotificationAttributes updateAutobundledSummaryIcon(@NonNull String packageName, + NotificationAttributes updateAutobundledSummaryAttributes(@NonNull String packageName, @NonNull List<NotificationAttributes> childrenAttr, @NonNull NotificationAttributes oldAttr) { - NotificationAttributes newAttr = getAutobundledSummaryIconAndColor(packageName, + NotificationAttributes newAttr = getAutobundledSummaryAttributes(packageName, childrenAttr); Icon newIcon = newAttr.icon; int newColor = newAttr.iconColor; @@ -321,7 +334,7 @@ public class GroupHelper { newColor = oldAttr.iconColor; } - return new NotificationAttributes(oldAttr.flags, newIcon, newColor); + return new NotificationAttributes(oldAttr.flags, newIcon, newColor, newAttr.visibility); } /** @@ -358,17 +371,20 @@ public class GroupHelper { public final int flags; public final int iconColor; public final Icon icon; + public final int visibility; - public NotificationAttributes(int flags, Icon icon, int iconColor) { + public NotificationAttributes(int flags, Icon icon, int iconColor, int visibility) { this.flags = flags; this.icon = icon; this.iconColor = iconColor; + this.visibility = visibility; } public NotificationAttributes(@NonNull NotificationAttributes attr) { this.flags = attr.flags; this.icon = attr.icon; this.iconColor = attr.iconColor; + this.visibility = attr.visibility; } @Override @@ -379,12 +395,13 @@ public class GroupHelper { if (!(o instanceof NotificationAttributes that)) { return false; } - return flags == that.flags && iconColor == that.iconColor && icon.sameAs(that.icon); + return flags == that.flags && iconColor == that.iconColor && icon.sameAs(that.icon) + && visibility == that.visibility; } @Override public int hashCode() { - return Objects.hash(flags, iconColor, icon); + return Objects.hash(flags, iconColor, icon, visibility); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ea4e67a17e50..d751186b2869 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1038,15 +1038,17 @@ public class NotificationManagerService extends SystemService { } int oldFlags = summary.getSbn().getNotification().flags; - boolean iconUpdated = + boolean attributesUpdated = !summaryAttr.icon.sameAs(summary.getSbn().getNotification().getSmallIcon()) - || summaryAttr.iconColor != summary.getSbn().getNotification().color; + || summaryAttr.iconColor != summary.getSbn().getNotification().color + || summaryAttr.visibility != summary.getSbn().getNotification().visibility; - if (oldFlags != summaryAttr.flags || iconUpdated) { + if (oldFlags != summaryAttr.flags || attributesUpdated) { summary.getSbn().getNotification().flags = summaryAttr.flags != GroupHelper.FLAG_INVALID ? summaryAttr.flags : oldFlags; summary.getSbn().getNotification().setSmallIcon(summaryAttr.icon); summary.getSbn().getNotification().color = summaryAttr.iconColor; + summary.getSbn().getNotification().visibility = summaryAttr.visibility; mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground, mPostNotificationTrackerFactory.newTracker(null))); } @@ -2939,7 +2941,8 @@ public class NotificationManagerService extends SystemService { public void addAutoGroupSummary(int userId, String pkg, String triggeringKey, NotificationAttributes summaryAttr) { NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, - summaryAttr.flags, summaryAttr.icon, summaryAttr.iconColor); + summaryAttr.flags, summaryAttr.icon, summaryAttr.iconColor, + summaryAttr.visibility); if (r != null) { final boolean isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; @@ -6725,7 +6728,7 @@ public class NotificationManagerService extends SystemService { // Creates a 'fake' summary for a package that has exceeded the solo-notification limit. NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey, - int flagsToSet, Icon summaryIcon, int summaryIconColor) { + int flagsToSet, Icon summaryIcon, int summaryIconColor, int summaryVisibilty) { NotificationRecord summaryRecord = null; boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId); synchronized (mNotificationLock) { @@ -6760,6 +6763,7 @@ public class NotificationManagerService extends SystemService { .setGroup(GroupHelper.AUTOGROUP_KEY) .setFlag(flagsToSet, true) .setColor(summaryIconColor) + .setVisibility(summaryVisibilty) .build(); summaryNotification.extras.putAll(extras); Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg); diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 295528e14ca7..f9d81127fd86 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -1625,10 +1625,11 @@ public class LauncherAppsService extends SystemService { } @Override + @NonNull public List<String> getPreInstalledSystemPackages(UserHandle user) { if (!canAccessProfile(user.getIdentifier(), "Can't access preinstalled packages for another user")) { - return null; + return new ArrayList<>(); } final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index d644235b8714..449e9ab8c5ec 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -240,7 +240,7 @@ class ShortcutPackage extends ShortcutPackageItem { @Override protected boolean canRestoreAnyVersion() { - return false; + return true; } @Override diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index a9d2858fd36e..c2f74a8895cb 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -232,7 +232,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_UNMUTE_MICROPHONE, UserManager.DISALLOW_UNMUTE_DEVICE, UserManager.DISALLOW_CAMERA, - UserManager.DISALLOW_ASSIST_CONTENT + UserManager.DISALLOW_ASSIST_CONTENT, + UserManager.DISALLOW_CONFIG_DEFAULT_APPS ); /** @@ -288,7 +289,8 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_SMS, UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, - UserManager.DISALLOW_UNMUTE_MICROPHONE + UserManager.DISALLOW_UNMUTE_MICROPHONE, + UserManager.DISALLOW_CONFIG_DEFAULT_APPS ); /** diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index 59766ec7a175..f8c678aa3aa3 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -45,6 +45,11 @@ import static android.hardware.SensorPrivacyManager.Sources.OTHER; import static android.hardware.SensorPrivacyManager.Sources.QS_TILE; import static android.hardware.SensorPrivacyManager.Sources.SETTINGS; import static android.hardware.SensorPrivacyManager.Sources.SHELL; +import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; +import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; +import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; +import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED; +import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED; import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE; import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE; import static android.os.UserHandle.USER_NULL; @@ -52,6 +57,9 @@ import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; +import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; +import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON; import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA; @@ -63,8 +71,11 @@ import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_ import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.write; +import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -87,6 +98,7 @@ import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.drawable.Icon; +import android.hardware.CameraPrivacyAllowlistEntry; import android.hardware.ISensorPrivacyListener; import android.hardware.ISensorPrivacyManager; import android.hardware.SensorPrivacyManager; @@ -123,6 +135,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; @@ -131,6 +144,7 @@ import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.pm.UserManagerInternal; @@ -139,6 +153,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; @@ -154,7 +169,24 @@ public final class SensorPrivacyService extends SystemService { SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy"; public static final int REMINDER_DIALOG_DELAY_MILLIS = 500; - + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__TOGGLE_ON = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__TOGGLE_OFF = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF; + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private static final int ACTION__ACTION_UNKNOWN = + PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN; private final Context mContext; private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl; private final UserManagerInternal mUserManagerInternal; @@ -176,6 +208,9 @@ public final class SensorPrivacyService extends SystemService { private CallStateHelper mCallStateHelper; private KeyguardManager mKeyguardManager; + List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist = + new ArrayList<CameraPrivacyAllowlistEntry>(); + private int mCurrentUser = USER_NULL; public SensorPrivacyService(Context context) { @@ -192,6 +227,15 @@ public final class SensorPrivacyService extends SystemService { mPackageManagerInternal = getLocalService(PackageManagerInternal.class); mNotificationManager = mContext.getSystemService(NotificationManager.class); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(); + ArrayMap<String, Boolean> cameraPrivacyAllowlist = + SystemConfig.getInstance().getCameraPrivacyAllowlist(); + + for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) { + CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry(); + ent.packageName = entry.getKey(); + ent.isMandatory = entry.getValue(); + mCameraPrivacyAllowlist.add(ent); + } } @Override @@ -324,8 +368,15 @@ public final class SensorPrivacyService extends SystemService { mHandler, mHandler::handleSensorPrivacyChanged); mSensorPrivacyStateController.setSensorPrivacyListener( mHandler, - (toggleType, userId, sensor, state) -> mHandler.handleSensorPrivacyChanged( - userId, toggleType, sensor, state.isEnabled())); + (toggleType, userId, sensor, state) -> { + mHandler.handleSensorPrivacyChanged( + userId, toggleType, sensor, state.isEnabled()); + if (Flags.cameraPrivacyAllowlist()) { + mHandler.handleSensorPrivacyChanged( + userId, toggleType, sensor, state.getState()); + } + }); + } // If sensor privacy is enabled for a sensor, but the device doesn't support sensor privacy @@ -400,9 +451,15 @@ public final class SensorPrivacyService extends SystemService { * @param packageName The package name of the app using the sensor * @param sensor The sensor that is attempting to be used */ + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) private void onSensorUseStarted(int uid, String packageName, int sensor) { UserHandle user = UserHandle.of(mCurrentUser); - if (!isCombinedToggleSensorPrivacyEnabled(sensor)) { + + if (Flags.cameraPrivacyAllowlist() && (sensor == CAMERA) && isAutomotive(mContext)) { + if (!isCameraPrivacyEnabled(packageName)) { + return; + } + } else if (!isCombinedToggleSensorPrivacyEnabled(sensor)) { return; } @@ -727,6 +784,12 @@ public final class SensorPrivacyService extends SystemService { == Configuration.UI_MODE_TYPE_TELEVISION; } + private boolean isAutomotive(Context context) { + int uiMode = context.getResources().getConfiguration().uiMode; + return (uiMode & Configuration.UI_MODE_TYPE_MASK) + == Configuration.UI_MODE_TYPE_CAR; + } + /** * Sets the sensor privacy to the provided state and notifies all listeners of the new * state. @@ -766,6 +829,225 @@ public final class SensorPrivacyService extends SystemService { setToggleSensorPrivacyUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, enable); } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setToggleSensorPrivacyState(int userId, int source, int sensor, int state) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " setToggleSensorPrivacyState(" + + "userId=" + userId + + " source=" + source + + " sensor=" + sensor + + " state=" + state + + ")"); + } + enforceManageSensorPrivacyPermission(); + if (userId == UserHandle.USER_CURRENT) { + userId = mCurrentUser; + } + + if (!canChangeToggleSensorPrivacy(userId, sensor)) { + return; + } + if (!supportsSensorToggle(TOGGLE_TYPE_SOFTWARE, sensor)) { + // Do not enable sensor privacy if the device doesn't support it. + return; + } + + setToggleSensorPrivacyStateUnchecked(TOGGLE_TYPE_SOFTWARE, userId, source, sensor, + state); + } + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private void setToggleSensorPrivacyStateUnchecked(int toggleType, int userId, int source, + int sensor, int state) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " setToggleSensorPrivacyStateUnchecked(" + + "userId=" + userId + + " source=" + source + + " sensor=" + sensor + + " state=" + state + + ")"); + } + long[] lastChange = new long[1]; + mSensorPrivacyStateController.atomic(() -> { + SensorState sensorState = mSensorPrivacyStateController + .getState(toggleType, userId, sensor); + lastChange[0] = sensorState.getLastChange(); + mSensorPrivacyStateController.setState( + toggleType, userId, sensor, state, mHandler, + changeSuccessful -> { + if (changeSuccessful) { + if (userId == mUserManagerInternal.getProfileParentId(userId)) { + mHandler.sendMessage(PooledLambda.obtainMessage( + SensorPrivacyServiceImpl::logSensorPrivacyStateToggle, + this, + source, sensor, state, lastChange[0], false)); + } + } + }); + }); + } + + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + private void logSensorPrivacyStateToggle(int source, int sensor, int state, + long lastChange, boolean onShutDown) { + long logMins = Math.max(0, (getCurrentTimeMillis() - lastChange) / (1000 * 60)); + + int logAction = ACTION__ACTION_UNKNOWN; + if (!onShutDown) { + switch(state) { + case ENABLED : + logAction = ACTION__TOGGLE_OFF; + break; + case DISABLED : + logAction = ACTION__TOGGLE_ON; + break; + case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS : + logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS; + break; + case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS : + logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS; + break; + case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS : + logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS; + break; + default : + logAction = ACTION__ACTION_UNKNOWN; + break; + } + } + + int logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN; + switch(sensor) { + case CAMERA: + logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA; + break; + case MICROPHONE: + logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE; + break; + default: + logSensor = PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN; + break; + } + + int logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN; + switch(source) { + case QS_TILE : + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__QS_TILE; + break; + case DIALOG : + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__DIALOG; + break; + case SETTINGS: + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SETTINGS; + break; + default: + logSource = PRIVACY_SENSOR_TOGGLE_INTERACTION__SOURCE__SOURCE_UNKNOWN; + break; + } + + if (DEBUG || DEBUG_LOGGING) { + Log.d(TAG, "Logging sensor toggle interaction:" + " logSensor=" + logSensor + + " logAction=" + logAction + " logSource=" + logSource + " logMins=" + + logMins); + } + write(PRIVACY_SENSOR_TOGGLE_INTERACTION, logSensor, logAction, logSource, logMins); + + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setToggleSensorPrivacyStateForProfileGroup(int userId, int source, int sensor, + int state) { + enforceManageSensorPrivacyPermission(); + if (userId == UserHandle.USER_CURRENT) { + userId = mCurrentUser; + } + int parentId = mUserManagerInternal.getProfileParentId(userId); + forAllUsers(userId2 -> { + if (parentId == mUserManagerInternal.getProfileParentId(userId2)) { + setToggleSensorPrivacyState(userId2, source, sensor, state); + } + }); + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() { + enforceObserveSensorPrivacyPermission(); + return mCameraPrivacyAllowlist; + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean isCameraPrivacyEnabled(String packageName) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " isCameraPrivacyEnabled(" + + "packageName=" + packageName + + ")"); + } + enforceObserveSensorPrivacyPermission(); + + int state = mSensorPrivacyStateController.getState(TOGGLE_TYPE_SOFTWARE, mCurrentUser, + CAMERA).getState(); + if (state == ENABLED) { + return true; + } else if (state == DISABLED) { + return false; + } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) { + for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { + if ((packageName.equals(entry.packageName)) && !entry.isMandatory) { + return false; + } + } + return true; + } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) { + for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { + if ((packageName.equals(entry.packageName)) && entry.isMandatory) { + return false; + } + } + return true; + } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) { + for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) { + if (packageName.equals(entry.packageName)) { + return false; + } + } + return true; + } + return false; + } + + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public int getToggleSensorPrivacyState(int toggleType, int sensor) { + if (DEBUG) { + Log.d(TAG, "callingUid=" + Binder.getCallingUid() + + " callingPid=" + Binder.getCallingPid() + + " getToggleSensorPrivacyState(" + + "toggleType=" + toggleType + + " sensor=" + sensor + + ")"); + } + enforceObserveSensorPrivacyPermission(); + + return mSensorPrivacyStateController.getState(toggleType, mCurrentUser, sensor) + .getState(); + } + private void setToggleSensorPrivacyUnchecked(int toggleType, int userId, int source, int sensor, boolean enable) { if (DEBUG) { @@ -899,16 +1181,23 @@ public final class SensorPrivacyService extends SystemService { * Enforces the caller contains the necessary permission to change the state of sensor * privacy. */ + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) private void enforceManageSensorPrivacyPermission() { - enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY, - "Changing sensor privacy requires the following permission: " - + MANAGE_SENSOR_PRIVACY); + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { + return; + } + + String message = "Changing sensor privacy requires the following permission: " + + MANAGE_SENSOR_PRIVACY; + throw new SecurityException(message); } /** * Enforces the caller contains the necessary permission to observe changes to the sate of * sensor privacy. */ + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) private void enforceObserveSensorPrivacyPermission() { String systemUIPackage = mContext.getString(R.string.config_systemUi); int systemUIAppId = UserHandle.getAppId(mPackageManagerInternal @@ -917,15 +1206,13 @@ public final class SensorPrivacyService extends SystemService { // b/221782106, possible race condition with role grant might bootloop device. return; } - enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY, - "Observing sensor privacy changes requires the following permission: " - + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY); - } - - private void enforcePermission(String permission, String message) { - if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { + if (mContext.checkCallingOrSelfPermission( + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { return; } + + String message = "Observing sensor privacy changes requires the following permission: " + + android.Manifest.permission.OBSERVE_SENSOR_PRIVACY; throw new SecurityException(message); } @@ -1293,11 +1580,13 @@ public final class SensorPrivacyService extends SystemService { } @Override + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { (new ShellCommand() { @Override + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public int onCommand(String cmd) { if (cmd == null) { return handleDefaultCommands(cmd); @@ -1327,6 +1616,45 @@ public final class SensorPrivacyService extends SystemService { setToggleSensorPrivacy(userId, SHELL, sensor, false); } break; + case "automotive_driver_assistance_apps" : { + if (Flags.cameraPrivacyAllowlist()) { + int sensor = sensorStrToId(getNextArgRequired()); + if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { + pw.println("Command not valid for this sensor"); + return -1; + } + + setToggleSensorPrivacyState(userId, SHELL, sensor, + AUTOMOTIVE_DRIVER_ASSISTANCE_APPS); + } + } + break; + case "automotive_driver_assistance_helpful_apps" : { + if (Flags.cameraPrivacyAllowlist()) { + int sensor = sensorStrToId(getNextArgRequired()); + if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { + pw.println("Command not valid for this sensor"); + return -1; + } + + setToggleSensorPrivacyState(userId, SHELL, sensor, + AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS); + } + } + break; + case "automotive_driver_assistance_required_apps" : { + if (Flags.cameraPrivacyAllowlist()) { + int sensor = sensorStrToId(getNextArgRequired()); + if ((!isAutomotive(mContext)) || (sensor != CAMERA)) { + pw.println("Command not valid for this sensor"); + return -1; + } + + setToggleSensorPrivacyState(userId, SHELL, sensor, + AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS); + } + } + break; default: return handleDefaultCommands(cmd); } @@ -1349,6 +1677,24 @@ public final class SensorPrivacyService extends SystemService { pw.println(" disable USER_ID SENSOR"); pw.println(" Disable privacy for a certain sensor."); pw.println(""); + if (Flags.cameraPrivacyAllowlist()) { + if (isAutomotive(mContext)) { + pw.println(" automotive_driver_assistance_apps USER_ID SENSOR"); + pw.println(" Disable privacy for automotive apps which help you" + + " drive and apps which are required by OEM"); + pw.println(""); + pw.println(" automotive_driver_assistance_helpful_apps " + + "USER_ID SENSOR"); + pw.println(" Disable privacy for automotive apps which " + + "help you drive."); + pw.println(""); + pw.println(" automotive_driver_assistance_required_apps " + + "USER_ID SENSOR"); + pw.println(" Disable privacy for automotive apps which are " + + "required by OEM."); + pw.println(""); + } + } } }).exec(this, in, out, err, args, callback, resultReceiver); } @@ -1457,6 +1803,38 @@ public final class SensorPrivacyService extends SystemService { mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType); } + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + public void handleSensorPrivacyChanged(int userId, int toggleType, int sensor, + int state) { + if (userId == mCurrentUser) { + mSensorPrivacyServiceImpl.setGlobalRestriction(sensor, + mSensorPrivacyServiceImpl.isCombinedToggleSensorPrivacyEnabled(sensor)); + } + + if (userId != mCurrentUser) { + return; + } + synchronized (mListenerLock) { + try { + final int count = mToggleSensorListeners.beginBroadcast(); + for (int i = 0; i < count; i++) { + ISensorPrivacyListener listener = mToggleSensorListeners.getBroadcastItem( + i); + try { + listener.onSensorPrivacyStateChanged(toggleType, sensor, state); + } catch (RemoteException e) { + Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", + e); + } + } + } finally { + mToggleSensorListeners.finishBroadcast(); + } + } + + mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType); + } + public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key, IBinder token) { sendMessage(PooledLambda.obtainMessage( diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java index 96949589447c..14b13e0d3692 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateController.java @@ -16,9 +16,11 @@ package com.android.server.sensorprivacy; +import android.annotation.FlaggedApi; import android.os.Handler; import com.android.internal.annotations.GuardedBy; +import com.android.internal.camera.flags.Flags; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; @@ -51,6 +53,14 @@ abstract class SensorPrivacyStateController { } } + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + void setState(int toggleType, int userId, int sensor, int state, Handler callbackHandler, + SetStateResultCallback callback) { + synchronized (mLock) { + setStateLocked(toggleType, userId, sensor, state, callbackHandler, callback); + } + } + void setSensorPrivacyListener(Handler handler, SensorPrivacyListener listener) { synchronized (mLock) { @@ -128,6 +138,11 @@ abstract class SensorPrivacyStateController { Handler callbackHandler, SetStateResultCallback callback); @GuardedBy("mLock") + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + abstract void setStateLocked(int toggleType, int userId, int sensor, int state, + Handler callbackHandler, SetStateResultCallback callback); + + @GuardedBy("mLock") abstract void setSensorPrivacyListenerLocked(Handler handler, SensorPrivacyListener listener); diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java index 3dcb4cf996c4..4b491ce30923 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyStateControllerImpl.java @@ -16,8 +16,12 @@ package com.android.server.sensorprivacy; +import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED; + +import android.annotation.FlaggedApi; import android.os.Handler; +import com.android.internal.camera.flags.Flags; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.internal.util.function.pooled.PooledLambda; @@ -85,6 +89,33 @@ class SensorPrivacyStateControllerImpl extends SensorPrivacyStateController { sendSetStateCallback(callbackHandler, callback, false); } + @Override + @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST) + void setStateLocked(int toggleType, int userId, int sensor, int state, + Handler callbackHandler, SetStateResultCallback callback) { + // Changing the SensorState's mEnabled updates the timestamp of its last change. + // A nonexistent state -> unmuted should not set the timestamp. + SensorState lastState = mPersistedState.getState(toggleType, userId, sensor); + if (lastState == null) { + if (state == DISABLED) { + sendSetStateCallback(callbackHandler, callback, false); + return; + } else { + SensorState sensorState = new SensorState(state); + mPersistedState.setState(toggleType, userId, sensor, sensorState); + notifyStateChangeLocked(toggleType, userId, sensor, sensorState); + sendSetStateCallback(callbackHandler, callback, true); + return; + } + } + if (lastState.setState(state)) { + notifyStateChangeLocked(toggleType, userId, sensor, lastState); + sendSetStateCallback(callbackHandler, callback, true); + return; + } + sendSetStateCallback(callbackHandler, callback, false); + } + private void notifyStateChangeLocked(int toggleType, int userId, int sensor, SensorState sensorState) { if (mListenerHandler != null && mListener != null) { diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index d2f6701e313e..4af8c616b2bc 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -65,6 +65,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.net.Uri; +import android.os.BadParcelableException; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -630,16 +631,24 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements if (intent == null) { return null; } - Uri data = intent.getData(); - ClipData clip = intent.getClipData(); - if (data == null && clip == null) { - return null; - } + // Default userId for uris in the intent (if they don't specify it themselves) int contentUserHint = intent.getContentUserHint(); if (contentUserHint == UserHandle.USER_CURRENT) { contentUserHint = UserHandle.getUserId(callingUid); } + + if (android.security.Flags.contentUriPermissionApis()) { + enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(intent, contentUserHint, + mode, callingUid, requireContentUriPermissionFromCaller); + } + + Uri data = intent.getData(); + ClipData clip = intent.getClipData(); + if (data == null && clip == null) { + return null; + } + int targetUid; if (needed != null) { targetUid = needed.targetUid; @@ -733,6 +742,37 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements } } + private void enforceRequireContentUriPermissionFromCallerOnIntentExtraStream(Intent intent, + int contentUserHint, int mode, int callingUid, + @RequiredContentUriPermission Integer requireContentUriPermissionFromCaller) { + try { + final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class); + if (uri != null) { + final GrantUri grantUri = GrantUri.resolve(contentUserHint, uri, mode); + enforceRequireContentUriPermissionFromCaller( + requireContentUriPermissionFromCaller, grantUri, callingUid); + } + } catch (BadParcelableException e) { + Slog.w(TAG, "Failed to unparcel an URI in EXTRA_STREAM, skipping" + + " requireContentUriPermissionFromCaller: " + e); + } + + try { + final ArrayList<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, + Uri.class); + if (uris != null) { + for (int i = uris.size() - 1; i >= 0; i--) { + final GrantUri grantUri = GrantUri.resolve(contentUserHint, uris.get(i), mode); + enforceRequireContentUriPermissionFromCaller( + requireContentUriPermissionFromCaller, grantUri, callingUid); + } + } + } catch (BadParcelableException e) { + Slog.w(TAG, "Failed to unparcel an ArrayList of URIs in EXTRA_STREAM, skipping" + + " requireContentUriPermissionFromCaller: " + e); + } + } + @GuardedBy("mLock") private void readGrantedUriPermissionsLocked() { if (DEBUG) Slog.v(TAG, "readGrantedUriPermissions()"); diff --git a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java index 62a637ebde13..3077fb8aaee9 100644 --- a/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java +++ b/services/core/java/com/android/server/wearable/RemoteWearableSensingService.java @@ -257,6 +257,55 @@ final class RemoteWearableSensingService extends ServiceConnector.Impl<IWearable statusCallback)); } + /** + * Request the wearable to start hotword recognition. + * + * @param wearableHotwordCallback The callback to send hotword audio data and format to. + * @param statusCallback The callback for service status. + */ + public void startHotwordRecognition( + RemoteCallback wearableHotwordCallback, RemoteCallback statusCallback) { + if (DEBUG) { + Slog.i(TAG, "Starting to listen for hotword."); + } + var unused = + post( + service -> + service.startHotwordRecognition( + wearableHotwordCallback, statusCallback)); + } + + /** + * Request the wearable to stop hotword recognition. + * + * @param statusCallback The callback for service status. + */ + public void stopHotwordRecognition(RemoteCallback statusCallback) { + if (DEBUG) { + Slog.i(TAG, "Stopping hotword recognition."); + } + var unused = post(service -> service.stopHotwordRecognition(statusCallback)); + } + + /** + * Signals to the {@link WearableSensingService} that hotword audio data is accepted by the + * {@link android.service.voice.HotwordDetectionService} as valid hotword. + */ + public void onValidatedByHotwordDetectionService() { + if (DEBUG) { + Slog.i(TAG, "Requesting hotword audio data egress."); + } + var unused = post(service -> service.onValidatedByHotwordDetectionService()); + } + + /** Stops the active hotword audio stream from the wearable. */ + public void stopActiveHotwordAudio() { + if (DEBUG) { + Slog.i(TAG, "Stopping hotword audio."); + } + var unused = post(service -> service.stopActiveHotwordAudio()); + } + private static class SecureWearableConnectionContext { final ParcelFileDescriptor mSecureWearableConnection; final RemoteCallback mStatusCallback; diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java index 9ba4433523ee..2b43203628d9 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerPerUserService.java @@ -16,6 +16,8 @@ package com.android.server.wearable; +import static android.service.wearable.WearableSensingService.HOTWORD_AUDIO_STREAM_BUNDLE_KEY; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,18 +30,23 @@ import android.companion.CompanionDeviceManager; import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SharedMemory; +import android.service.voice.HotwordAudioStream; +import android.service.voice.VoiceInteractionManagerInternal; +import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback; import android.system.OsConstants; import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import com.android.server.infra.AbstractPerUserSystemService; import java.io.IOException; @@ -58,6 +65,7 @@ final class WearableSensingManagerPerUserService extends @VisibleForTesting RemoteWearableSensingService mRemoteService; + @Nullable private VoiceInteractionManagerInternal mVoiceInteractionManagerInternal; private ComponentName mComponentName; private final Object mSecureChannelLock = new Object(); @@ -99,6 +107,15 @@ final class WearableSensingManagerPerUserService extends } } + @GuardedBy("mLock") + private boolean ensureVoiceInteractionManagerInternalInitiated() { + if (mVoiceInteractionManagerInternal == null) { + mVoiceInteractionManagerInternal = + LocalServices.getService(VoiceInteractionManagerInternal.class); + } + return mVoiceInteractionManagerInternal != null; + } + /** * get the currently bound component name. */ @@ -334,4 +351,109 @@ final class WearableSensingManagerPerUserService extends dataType, dataRequestObserverId, packageName, statusCallback); } } + + /** Handles starting hotword listening. */ + public void onStartHotwordRecognition( + ComponentName targetVisComponentName, RemoteCallback statusCallback) { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + if (!ensureVoiceInteractionManagerInternalInitiated()) { + Slog.w(TAG, "Voice interaction manager is not available at this moment."); + notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + ensureRemoteServiceInitiated(); + mRemoteService.startHotwordRecognition( + createWearableHotwordCallback(targetVisComponentName), statusCallback); + } + } + + /** Handles stopping hotword listening. */ + public void onStopHotwordRecognition(RemoteCallback statusCallback) { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Detection service is not available at this moment."); + notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + ensureRemoteServiceInitiated(); + mRemoteService.stopHotwordRecognition(statusCallback); + } + } + + private void onValidatedByHotwordDetectionService() { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Wearable sensing service is not available at this moment."); + return; + } + ensureRemoteServiceInitiated(); + mRemoteService.onValidatedByHotwordDetectionService(); + } + } + + private void stopActiveHotwordAudio() { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Wearable sensing service is not available at this moment."); + return; + } + ensureRemoteServiceInitiated(); + mRemoteService.stopActiveHotwordAudio(); + } + } + + private RemoteCallback createWearableHotwordCallback(ComponentName targetVisComponentName) { + return new RemoteCallback( + result -> { + HotwordAudioStream hotwordAudioStream = + result.getParcelable( + HOTWORD_AUDIO_STREAM_BUNDLE_KEY, HotwordAudioStream.class); + if (hotwordAudioStream == null) { + Slog.w(TAG, "No hotword audio stream received, unable to process hotword."); + return; + } + final long identity = Binder.clearCallingIdentity(); + try { + mVoiceInteractionManagerInternal.startListeningFromWearable( + hotwordAudioStream.getAudioStreamParcelFileDescriptor(), + hotwordAudioStream.getAudioFormat(), + hotwordAudioStream.getMetadata(), + targetVisComponentName, + getUserId(), + createHotwordDetectionCallback()); + } finally { + Binder.restoreCallingIdentity(identity); + } + }); + } + + private WearableHotwordDetectionCallback createHotwordDetectionCallback() { + return new WearableHotwordDetectionCallback() { + @Override + public void onDetected() { + Slog.i(TAG, "hotwordDetectionCallback onDetected."); + onValidatedByHotwordDetectionService(); + } + + @Override + public void onRejected() { + Slog.i(TAG, "hotwordDetectionCallback onRejected."); + stopActiveHotwordAudio(); + } + + @Override + public void onError(String errorMessage) { + Slog.i(TAG, "hotwordDetectionCallback onError. ErrorMessage: " + errorMessage); + stopActiveHotwordAudio(); + } + }; + } } diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java index 78952fa4590b..00c3026b1194 100644 --- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java +++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java @@ -496,6 +496,42 @@ public class WearableSensingManagerService extends } @Override + public void startHotwordRecognition( + ComponentName targetVisComponentName, RemoteCallback statusCallback) { + Slog.i(TAG, "WearableSensingManagerInternal startHotwordRecognition."); + Objects.requireNonNull(statusCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + WearableSensingManagerPerUserService.notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + callPerUserServiceIfExist( + service -> + service.onStartHotwordRecognition( + targetVisComponentName, statusCallback), + statusCallback); + } + + @Override + public void stopHotwordRecognition(RemoteCallback statusCallback) { + Slog.i(TAG, "WearableSensingManagerInternal stopHotwordRecognition."); + Objects.requireNonNull(statusCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + WearableSensingManagerPerUserService.notifyStatusCallback( + statusCallback, WearableSensingManager.STATUS_SERVICE_UNAVAILABLE); + return; + } + callPerUserServiceIfExist( + service -> service.onStopHotwordRecognition(statusCallback), statusCallback); + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { new WearableSensingShellCommand(WearableSensingManagerService.this).exec( diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java index a54dd826b5bb..3609837f417b 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java @@ -23,18 +23,20 @@ import android.window.TaskSnapshot; */ class ActivitySnapshotCache extends SnapshotCache<ActivityRecord> { - ActivitySnapshotCache(WindowManagerService service) { - super(service, "Activity"); + ActivitySnapshotCache() { + super("Activity"); } @Override void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) { final int hasCode = System.identityHashCode(ar); - final CacheEntry entry = mRunningCache.get(hasCode); - if (entry != null) { - mAppIdMap.remove(entry.topApp); + synchronized (mLock) { + final CacheEntry entry = mRunningCache.get(hasCode); + if (entry != null) { + mAppIdMap.remove(entry.topApp); + } + mAppIdMap.put(ar, hasCode); + mRunningCache.put(hasCode, new CacheEntry(snapshot, ar)); } - mAppIdMap.put(ar, hasCode); - mRunningCache.put(hasCode, new CacheEntry(snapshot, ar)); } } diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java index 1f013b913283..f83003d4e278 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java @@ -102,7 +102,7 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord Environment::getDataSystemCeDirectory); mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider); mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider); - initialize(new ActivitySnapshotCache(service)); + initialize(new ActivitySnapshotCache()); final boolean snapshotEnabled = !service.mContext diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index b65b2b2ae959..2d6f03a6c9ad 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -913,8 +913,10 @@ public class BackgroundActivityStartController { // Normal apps with visible app window will be allowed to start activity if app switching // is allowed, or apps like live wallpaper with non app visible window will be allowed. + // The home app can start apps even if app switches are usually disallowed. final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW - || state.mAppSwitchState == APP_SWITCH_FG_ONLY; + || state.mAppSwitchState == APP_SWITCH_FG_ONLY + || isHomeApp(state.mRealCallingUid, state.mRealCallingPackage); if (balImproveRealCallerVisibilityCheck()) { if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) { return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index e7431723789d..25a5fca1432c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -6783,6 +6783,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mSandboxDisplayApis; } + /** + * For testing only; inject a ContentRecorder instance. + */ + @VisibleForTesting + void setContentRecorder(ContentRecorder contentRecorder) { + mContentRecorder = contentRecorder; + } + private ContentRecorder getContentRecorder() { if (mContentRecorder == null) { mContentRecorder = new ContentRecorder(this); diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index 401b2604c28f..86804360f6f4 100644 --- a/services/core/java/com/android/server/wm/SnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -19,6 +19,8 @@ import android.annotation.Nullable; import android.util.ArrayMap; import android.window.TaskSnapshot; +import com.android.internal.annotations.GuardedBy; + import java.io.PrintWriter; /** @@ -26,25 +28,31 @@ import java.io.PrintWriter; * @param <TYPE> The basic type, either Task or ActivityRecord */ abstract class SnapshotCache<TYPE extends WindowContainer> { - protected final WindowManagerService mService; + protected final Object mLock = new Object(); + protected final String mName; + + @GuardedBy("mLock") protected final ArrayMap<ActivityRecord, Integer> mAppIdMap = new ArrayMap<>(); + + @GuardedBy("mLock") protected final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>(); - SnapshotCache(WindowManagerService service, String name) { - mService = service; + SnapshotCache(String name) { mName = name; } abstract void putSnapshot(TYPE window, TaskSnapshot snapshot); void clearRunningCache() { - mRunningCache.clear(); + synchronized (mLock) { + mRunningCache.clear(); + } } @Nullable final TaskSnapshot getSnapshot(Integer id) { - synchronized (mService.mGlobalLock) { + synchronized (mLock) { // Try the running cache. final CacheEntry entry = mRunningCache.get(id); if (entry != null) { @@ -56,17 +64,21 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { /** Called when an app token has been removed. */ void onAppRemoved(ActivityRecord activity) { - final Integer id = mAppIdMap.get(activity); - if (id != null) { - removeRunningEntry(id); + synchronized (mLock) { + final Integer id = mAppIdMap.get(activity); + if (id != null) { + removeRunningEntry(id); + } } } /** Called when an app window token's process died. */ void onAppDied(ActivityRecord activity) { - final Integer id = mAppIdMap.get(activity); - if (id != null) { - removeRunningEntry(id); + synchronized (mLock) { + final Integer id = mAppIdMap.get(activity); + if (id != null) { + removeRunningEntry(id); + } } } @@ -75,10 +87,12 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { } void removeRunningEntry(Integer id) { - final CacheEntry entry = mRunningCache.get(id); - if (entry != null) { - mAppIdMap.remove(entry.topApp); - mRunningCache.remove(id); + synchronized (mLock) { + final CacheEntry entry = mRunningCache.get(id); + if (entry != null) { + mAppIdMap.remove(entry.topApp); + mRunningCache.remove(id); + } } } @@ -86,11 +100,14 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { final String doublePrefix = prefix + " "; final String triplePrefix = doublePrefix + " "; pw.println(prefix + "SnapshotCache " + mName); - for (int i = mRunningCache.size() - 1; i >= 0; i--) { - final CacheEntry entry = mRunningCache.valueAt(i); - pw.println(doublePrefix + "Entry token=" + mRunningCache.keyAt(i)); - pw.println(triplePrefix + "topApp=" + entry.topApp); - pw.println(triplePrefix + "snapshot=" + entry.snapshot); + + synchronized (mLock) { + for (int i = mRunningCache.size() - 1; i >= 0; i--) { + final CacheEntry entry = mRunningCache.valueAt(i); + pw.println(doublePrefix + "Entry token=" + mRunningCache.keyAt(i)); + pw.println(triplePrefix + "topApp=" + entry.topApp); + pw.println(triplePrefix + "snapshot=" + entry.snapshot); + } } } diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index f4e995719b6d..3917868f28c5 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -303,15 +303,9 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { } else { if (DEBUG) appendLog("non-freeform-task-display-area"); } - final boolean isUpdatingExistingTaskWindowingMode = task != null - && task.getRequestedOverrideWindowingMode() != WINDOWING_MODE_UNDEFINED - && launchMode != task.getRequestedOverrideWindowingMode(); - if (DEBUG && isUpdatingExistingTaskWindowingMode) { - appendLog("updating-existing-task-windowing-mode"); - } // If launch mode matches display windowing mode, let it inherit from display. outParams.mWindowingMode = launchMode == suggestedDisplayArea.getWindowingMode() - && !isUpdatingExistingTaskWindowingMode + && !shouldUpdateExistingTaskWindowingMode(task, launchMode) ? WINDOWING_MODE_UNDEFINED : launchMode; if (phase == PHASE_WINDOWING_MODE) { @@ -403,6 +397,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { return RESULT_CONTINUE; } + private boolean shouldUpdateExistingTaskWindowingMode(Task task, int launchMode) { + return task != null + && task.getRequestedOverrideWindowingMode() != WINDOWING_MODE_UNDEFINED + && task.getRequestedOverrideWindowingMode() != WINDOWING_MODE_PINNED + && launchMode != task.getRequestedOverrideWindowingMode(); + } + private TaskDisplayArea getPreferredLaunchTaskDisplayArea(@Nullable Task task, @Nullable ActivityOptions options, @Nullable ActivityRecord source, @Nullable LaunchParams currentParams, @Nullable ActivityRecord activityRecord, diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java index 33486ccb995f..b69ac1bb2795 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java @@ -28,19 +28,21 @@ class TaskSnapshotCache extends SnapshotCache<Task> { private final AppSnapshotLoader mLoader; - TaskSnapshotCache(WindowManagerService service, AppSnapshotLoader loader) { - super(service, "Task"); + TaskSnapshotCache(AppSnapshotLoader loader) { + super("Task"); mLoader = loader; } void putSnapshot(Task task, TaskSnapshot snapshot) { - final CacheEntry entry = mRunningCache.get(task.mTaskId); - if (entry != null) { - mAppIdMap.remove(entry.topApp); + synchronized (mLock) { + final CacheEntry entry = mRunningCache.get(task.mTaskId); + if (entry != null) { + mAppIdMap.remove(entry.topApp); + } + final ActivityRecord top = task.getTopMostActivity(); + mAppIdMap.put(top, task.mTaskId); + mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top)); } - final ActivityRecord top = task.getTopMostActivity(); - mAppIdMap.put(top, task.mTaskId); - mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top)); } /** diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index d8e18e47fa89..8b622d28b651 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -68,7 +68,7 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot Environment::getDataSystemCeDirectory); mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider); - initialize(new TaskSnapshotCache(service, new AppSnapshotLoader(mPersistInfoProvider))); + initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider))); final boolean snapshotEnabled = !service.mContext .getResources() diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java index 817901f96ad4..fa2d9bf21dee 100644 --- a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java +++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java @@ -322,15 +322,17 @@ public class TrustedPresentationListenerController { var listener = trustedPresentationInfo.mListener; boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState; boolean newState = - (alpha >= trustedPresentationInfo.mThresholds.minAlpha) && (fractionRendered - >= trustedPresentationInfo.mThresholds.minFractionRendered); + (alpha >= trustedPresentationInfo.mThresholds.getMinAlpha()) + && (fractionRendered >= trustedPresentationInfo.mThresholds + .getMinFractionRendered()); trustedPresentationInfo.mLastComputedTrustedPresentationState = newState; ProtoLog.v(WM_DEBUG_TPL, "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f " + "minFractionRendered=%f", - lastState, newState, alpha, trustedPresentationInfo.mThresholds.minAlpha, - fractionRendered, trustedPresentationInfo.mThresholds.minFractionRendered); + lastState, newState, alpha, trustedPresentationInfo.mThresholds.getMinAlpha(), + fractionRendered, trustedPresentationInfo.mThresholds + .getMinFractionRendered()); if (lastState && !newState) { // We were in the trusted presentation state, but now we left it, @@ -350,13 +352,15 @@ public class TrustedPresentationListenerController { trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs; mHandler.postDelayed(() -> { computeTpl(mLastWindowHandles); - }, (long) (trustedPresentationInfo.mThresholds.stabilityRequirementMs * 1.5)); + }, (long) (trustedPresentationInfo.mThresholds + .getStabilityRequirementMillis() * 1.5)); } // Has the timer elapsed, but we are still in the state? Emit a callback if needed if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && ( currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime - > trustedPresentationInfo.mThresholds.stabilityRequirementMs)) { + > trustedPresentationInfo.mThresholds + .getStabilityRequirementMillis())) { trustedPresentationInfo.mLastReportedTrustedPresentationState = true; addListenerUpdate(listenerUpdates, listener, trustedPresentationInfo.mId, /*presentationState*/ true); @@ -413,15 +417,6 @@ public class TrustedPresentationListenerController { mThresholds = thresholds; mId = id; mListener = listener; - checkValid(thresholds); - } - - private void checkValid(TrustedPresentationThresholds thresholds) { - if (thresholds.minAlpha <= 0 || thresholds.minFractionRendered <= 0 - || thresholds.stabilityRequirementMs < 1) { - throw new IllegalArgumentException( - "TrustedPresentationThresholds values are invalid"); - } } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 105dc880c9a7..0ccf810c720d 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -102,6 +102,9 @@ final class DevicePolicyEngine { DevicePolicyIdentifiers.getIdentifierForUserRestriction( UserManager.DISALLOW_CELLULAR_2G); + //TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit + private static final int DEFAULT_POLICY_SIZE_LIMIT = -1; + private final Context mContext; private final UserManager mUserManager; @@ -122,10 +125,11 @@ final class DevicePolicyEngine { * Map containing the current set of admins in each user with active policies. */ private final SparseArray<Set<EnforcingAdmin>> mEnforcingAdmins; + private final SparseArray<HashMap<EnforcingAdmin, Integer>> mAdminPolicySize; - //TODO(b/295504706) : Speak to security team to decide what to set Policy_Size_Limit - private static final int POLICY_SIZE_LIMIT = 99999; + private int mPolicySizeLimit = DEFAULT_POLICY_SIZE_LIMIT; + private final DeviceAdminServiceController mDeviceAdminServiceController; DevicePolicyEngine( @@ -1594,7 +1598,9 @@ final class DevicePolicyEngine { existingPolicySize = sizeOf(policyState.getPoliciesSetByAdmins().get(admin)); } int policySize = sizeOf(value); - if (currentAdminPoliciesSize + policySize - existingPolicySize < POLICY_SIZE_LIMIT) { + // Policy size limit is disabled if mPolicySizeLimit is -1. + if (mPolicySizeLimit == -1 + || currentAdminPoliciesSize + policySize - existingPolicySize < mPolicySizeLimit) { increasePolicySizeForAdmin( admin, /* policySizeDiff = */ policySize - existingPolicySize); return true; @@ -1642,6 +1648,26 @@ final class DevicePolicyEngine { } } + /** + * Updates the max allowed size limit for policies per admin. Setting it to -1, disables + * the limitation. + */ + void setMaxPolicyStorageLimit(int storageLimit) { + if (storageLimit < DEFAULT_POLICY_SIZE_LIMIT && storageLimit != -1) { + throw new IllegalArgumentException("Can't set a size limit less than the minimum " + + "allowed size."); + } + mPolicySizeLimit = storageLimit; + } + + /** + * Returns the max allowed size limit for policies per admin. -1 means the limitation is + * disabled. + */ + int getMaxPolicyStorageLimit() { + return mPolicySizeLimit; + } + public void dump(IndentingPrintWriter pw) { synchronized (mLock) { pw.println("Local Policies: "); @@ -1761,6 +1787,7 @@ final class DevicePolicyEngine { private static final String TAG_ENFORCING_ADMIN_AND_SIZE = "enforcing-admin-and-size"; private static final String TAG_ENFORCING_ADMIN = "enforcing-admin"; private static final String TAG_POLICY_SUM_SIZE = "policy-sum-size"; + private static final String TAG_MAX_POLICY_SIZE_LIMIT = "max-policy-size-limit"; private static final String ATTR_USER_ID = "user-id"; private static final String ATTR_POLICY_SUM_SIZE = "size"; @@ -1805,6 +1832,7 @@ final class DevicePolicyEngine { writeGlobalPoliciesInner(serializer); writeEnforcingAdminsInner(serializer); writeEnforcingAdminSizeInner(serializer); + writeMaxPolicySizeInner(serializer); } private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException { @@ -1886,6 +1914,17 @@ final class DevicePolicyEngine { } } + private void writeMaxPolicySizeInner(TypedXmlSerializer serializer) + throws IOException { + if (!devicePolicySizeTrackingEnabled()) { + return; + } + serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT); + serializer.attributeInt( + /* namespace= */ null, ATTR_POLICY_SUM_SIZE, mPolicySizeLimit); + serializer.endTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT); + } + void readFromFileLocked() { if (!mFile.exists()) { Log.d(TAG, "" + mFile + " doesn't exist"); @@ -1926,6 +1965,9 @@ final class DevicePolicyEngine { case TAG_ENFORCING_ADMIN_AND_SIZE: readEnforcingAdminAndSizeInner(parser); break; + case TAG_MAX_POLICY_SIZE_LIMIT: + readMaxPolicySizeInner(parser); + break; default: Slogf.wtf(TAG, "Unknown tag " + tag); } @@ -2036,5 +2078,13 @@ final class DevicePolicyEngine { } mAdminPolicySize.get(admin.getUserId()).put(admin, size); } + + private void readMaxPolicySizeInner(TypedXmlPullParser parser) + throws XmlPullParserException, IOException { + if (!devicePolicySizeTrackingEnabled()) { + return; + } + mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE); + } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 51eee64d12f2..e619440b580b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -60,6 +60,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PRINTING; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS; @@ -85,6 +86,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WINDOWS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA; import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.Manifest.permission.MASTER_CLEAR; +import static android.Manifest.permission.NOTIFY_PENDING_SYSTEM_UPDATE; import static android.Manifest.permission.QUERY_ADMIN_POLICY; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; @@ -101,6 +103,7 @@ import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTI import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED; import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; +import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK; import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA; import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED; @@ -235,8 +238,10 @@ import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled; import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled; import static android.app.admin.flags.Flags.headlessDeviceOwnerSingleUserEnabled; +import static android.app.admin.flags.Flags.permissionMigrationForZeroTrustImplEnabled; import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled; import static android.app.admin.flags.Flags.assistContentUserRestrictionEnabled; +import static android.app.admin.flags.Flags.securityLogV2Enabled; import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE; import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -3376,9 +3381,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { applyProfileRestrictionsIfDeviceOwnerLocked(); // TODO: Is this the right place to trigger the migration? - if (shouldMigrateToDevicePolicyEngine()) { - migratePoliciesToDevicePolicyEngine(); + if (shouldMigrateV1ToDevicePolicyEngine()) { + migrateV1PoliciesToDevicePolicyEngine(); } + migratePoliciesToPolicyEngineLocked(); } maybeStartSecurityLogMonitorOnActivityManagerReady(); break; @@ -3392,6 +3398,48 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @GuardedBy("getLockObject()") + private void maybeMigrateSecurityLoggingPolicyLocked() { + if (!securityLogV2Enabled() || mOwners.isSecurityLoggingMigrated()) { + return; + } + + try { + migrateSecurityLoggingPolicyInternalLocked(); + } catch (Exception e) { + Slog.e(LOG_TAG, "Failed to properly migrate security logging to policy engine", e); + } + + Slog.i(LOG_TAG, "Marking security logging policy migration complete"); + mOwners.markSecurityLoggingMigrated(); + } + + @GuardedBy("getLockObject()") + private void migrateSecurityLoggingPolicyInternalLocked() { + Slog.i(LOG_TAG, "Migrating security logging policy to policy engine"); + if (!mInjector.securityLogGetLoggingEnabledProperty()) { + Slog.i(LOG_TAG, "Security logs not enabled, exiting"); + return; + } + + // Security logging can be enabled either by DO or by COPE PO. + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); + if (admin == null) { + Slog.wtf(LOG_TAG, "Security logging is enabled, but no appropriate admin found"); + return; + } + + EnforcingAdmin enforcingAdmin = + EnforcingAdmin.createEnterpriseEnforcingAdmin( + admin.info.getComponent(), + admin.getUserHandle().getIdentifier(), + admin); + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.SECURITY_LOGGING, + enforcingAdmin, + new BooleanPolicyValue(true)); + } + private void applyManagedSubscriptionsPolicyIfRequired() { int copeProfileUserId = getOrganizationOwnedProfileUserId(); // This policy is relevant only for COPE devices. @@ -9480,7 +9528,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private int getHeadlessDeviceOwnerMode() { synchronized (getLockObject()) { - return getDeviceOwnerAdminLocked().info.getHeadlessDeviceOwnerMode(); + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner == null) { + return HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; + } + return deviceOwner.info.getHeadlessDeviceOwnerMode(); } } @@ -11998,8 +12050,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (packageList != null) { - for (String pkg : packageList) { - PolicySizeVerifier.enforceMaxPackageNameLength(pkg); + if (!devicePolicySizeTrackingEnabled()) { + for (String pkg : packageList) { + PolicySizeVerifier.enforceMaxPackageNameLength(pkg); + } } List<InputMethodInfo> enabledImes = mInjector.binderWithCleanCallingIdentity(() -> @@ -12235,17 +12289,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(admin); + // Only allow the system user to use this method + Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(), + "createAndManageUser was called from non-system user"); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER); + if (headlessDeviceOwnerSingleUserEnabled()) { // Block this method if the device is in headless main user mode Preconditions.checkCallAuthorization( getHeadlessDeviceOwnerMode() != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER, "createAndManageUser was called while in headless single user mode"); } - // Only allow the system user to use this method - Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(), - "createAndManageUser was called from non-system user"); - Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); - checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_CREATE_AND_MANAGE_USER); final boolean ephemeral = (flags & DevicePolicyManager.MAKE_USER_EPHEMERAL) != 0; final boolean demo = (flags & DevicePolicyManager.MAKE_USER_DEMO) != 0 @@ -14318,8 +14373,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages) throws SecurityException { Objects.requireNonNull(packages, "packages is null"); - for (String pkg : packages) { - PolicySizeVerifier.enforceMaxPackageNameLength(pkg); + if (!devicePolicySizeTrackingEnabled()) { + for (String pkg : packages) { + PolicySizeVerifier.enforceMaxPackageNameLength(pkg); + } } CallerIdentity caller = getCallerIdentity(who, callerPackageName); @@ -15721,6 +15778,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return enforcingUsers; } + @Override + public void enforceSecurityLoggingPolicy(boolean enabled) { + enforceLoggingPolicy(enabled); + } + private List<EnforcingUser> getEnforcingUsers(Set<EnforcingAdmin> admins) { List<EnforcingUser> enforcingUsers = new ArrayList(); ComponentName deviceOwner = mOwners.getDeviceOwnerComponent(); @@ -15738,6 +15800,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void enforceLoggingPolicy(boolean securityLoggingEnabled) { + Slogf.i(LOG_TAG, "Enforcing security logging, securityLoggingEnabled: %b", + securityLoggingEnabled); + SecurityLog.setLoggingEnabledProperty(securityLoggingEnabled); + if (securityLoggingEnabled) { + mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); + synchronized (getLockObject()) { + maybePauseDeviceWideLoggingLocked(); + } + } else { + mSecurityLogMonitor.stop(); + } + } + private Intent createShowAdminSupportIntent(int userId) { // This method is called with AMS lock held, so don't take DPMS lock final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS); @@ -16180,7 +16256,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void notifyPendingSystemUpdate(@Nullable SystemUpdateInfo info) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.NOTIFY_PENDING_SYSTEM_UPDATE), + hasCallingOrSelfPermission(NOTIFY_PENDING_SYSTEM_UPDATE), "Only the system update service can broadcast update information"); mInjector.binderWithCleanCallingIdentity(() -> { @@ -16221,12 +16297,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Send broadcasts to corresponding profile owners if any. for (final int userId : runningUserIds) { + final ComponentName profileOwnerPackage; synchronized (getLockObject()) { - final ComponentName profileOwnerPackage = - mOwners.getProfileOwnerComponent(userId); - if (profileOwnerPackage != null) { - intent.setComponent(profileOwnerPackage); - mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + profileOwnerPackage = mOwners.getProfileOwnerComponent(userId); + } + if (profileOwnerPackage != null) { + intent.setComponent(profileOwnerPackage); + mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); + } + + if (permissionMigrationForZeroTrustImplEnabled()) { + final UserHandle user = UserHandle.of(userId); + final String roleHolderPackage = getRoleHolderPackageNameOnUser( + RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT, userId); + if (roleHolderPackage != null) { + broadcastExplicitIntentToPackage(intent, roleHolderPackage, user); } } } @@ -16234,13 +16319,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin) { - Objects.requireNonNull(admin, "ComponentName is null"); - - final CallerIdentity caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin, String callerPackage) { + if (permissionMigrationForZeroTrustImplEnabled()) { + CallerIdentity caller = getCallerIdentity(admin, callerPackage); + enforcePermissions(new String[] {NOTIFY_PENDING_SYSTEM_UPDATE, + MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES}, caller.getPackageName(), + caller.getUserId()); + } else { + Objects.requireNonNull(admin, "ComponentName is null"); + final CallerIdentity caller = getCallerIdentity(admin); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + } return mOwners.getSystemUpdateInfo(); } @@ -17586,19 +17677,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setSecurityLoggingEnabled(ComponentName admin, String packageName, + public void setSecurityLoggingEnabled(ComponentName who, String packageName, boolean enabled) { if (!mHasFeature) { return; } - final CallerIdentity caller = getCallerIdentity(admin, packageName); + final CallerIdentity caller = getCallerIdentity(who, packageName); - synchronized (getLockObject()) { - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), - UserHandle.USER_ALL); + if (securityLogV2Enabled()) { + EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin( + who, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, + caller.getPackageName(), + caller.getUserId()); + if (enabled) { + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.SECURITY_LOGGING, + admin, + new BooleanPolicyValue(true)); } else { - if (admin != null) { + mDevicePolicyEngine.removeGlobalPolicy( + PolicyDefinition.SECURITY_LOGGING, + admin); + } + } else { + synchronized (getLockObject()) { + if (who != null) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); @@ -17607,17 +17711,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization( isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); } - } - if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) { - return; - } - mInjector.securityLogSetLoggingEnabledProperty(enabled); - if (enabled) { - mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); - maybePauseDeviceWideLoggingLocked(); - } else { - mSecurityLogMonitor.stop(); + if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) { + return; + } + mInjector.securityLogSetLoggingEnabledProperty(enabled); + if (enabled) { + mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); + maybePauseDeviceWideLoggingLocked(); + } else { + mSecurityLogMonitor.stop(); + } } } DevicePolicyEventLogger @@ -17633,25 +17737,35 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } - synchronized (getLockObject()) { - if (!isSystemUid(getCallerIdentity())) { - final CallerIdentity caller = getCallerIdentity(admin, packageName); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, - caller.getPackageName(), UserHandle.USER_ALL); + final CallerIdentity caller = getCallerIdentity(admin, packageName); + if (isSystemUid(caller)) { + // Settings uses this for privacy transparency. + // TODO: create a separate @hidden API for settings. + return mInjector.securityLogGetLoggingEnabledProperty(); + } + + if (securityLogV2Enabled()) { + final EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + admin, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, + caller.getPackageName(), + caller.getUserId()); + final Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.SECURITY_LOGGING, enforcingAdmin); + return Boolean.TRUE.equals(policy); + } else { + synchronized (getLockObject()) { + if (admin != null) { + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) + || isDefaultDeviceOwner(caller)); } else { - if (admin != null) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); - } else { - // A delegate app passes a null admin component, which is expected - Preconditions.checkCallAuthorization( - isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); - } + // A delegate app passes a null admin component, which is expected + Preconditions.checkCallAuthorization( + isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); } + return mInjector.securityLogGetLoggingEnabledProperty(); } - return mInjector.securityLogGetLoggingEnabledProperty(); } } @@ -17727,17 +17841,32 @@ 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); + if (securityLogV2Enabled()) { + EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + admin, + MANAGE_DEVICE_POLICY_SECURITY_LOGGING, + caller.getPackageName(), + caller.getUserId()); + + synchronized (getLockObject()) { + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); + } + + Boolean policy = mDevicePolicyEngine.getGlobalPolicySetByAdmin( + PolicyDefinition.SECURITY_LOGGING, enforcingAdmin); + + if (!Boolean.TRUE.equals(policy)) { + Slogf.e(LOG_TAG, "%s hasn't enabled security logging but tries to retrieve logs", + caller.getPackageName()); + return null; + } } else { if (admin != null) { Preconditions.checkCallAuthorization( isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); + || isDefaultDeviceOwner(caller)); } else { // A delegate app passes a null admin component, which is expected Preconditions.checkCallAuthorization( @@ -17745,10 +17874,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() || areAllUsersAffiliatedWithDeviceLocked()); - } - if (!mInjector.securityLogGetLoggingEnabledProperty()) { - return null; + if (!mInjector.securityLogGetLoggingEnabledProperty()) { + return null; + } } recordSecurityLogRetrievalTime(); @@ -17758,7 +17887,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .createEvent(DevicePolicyEnums.RETRIEVE_SECURITY_LOGS) .setAdmin(caller.getPackageName()) .write(); - return logs != null ? new ParceledListSlice<SecurityEvent>(logs) : null; + return logs != null ? new ParceledListSlice<>(logs) : null; } @Override @@ -20706,14 +20835,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(callerPackage); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller) - || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + if (permissionMigrationForZeroTrustImplEnabled()) { + enforcePermission(MANAGE_DEVICE_POLICY_CERTIFICATES, caller.getPackageName()); + } else { + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller) + || isCallerDelegate(caller, DELEGATION_CERT_INSTALL)); + } synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked( caller.getUserId()); - final String esid = requiredAdmin.mEnrollmentSpecificId; + final String esid = requiredAdmin != null ? requiredAdmin.mEnrollmentSpecificId : null; return esid != null ? esid : ""; } } @@ -22384,7 +22517,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_WINDOWS, MANAGE_DEVICE_POLICY_WIPE_DATA, SET_TIME, - SET_TIME_ZONE + SET_TIME_ZONE, + MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES ); private static final List<String> FINANCED_DEVICE_OWNER_PERMISSIONS = List.of( MANAGE_DEVICE_POLICY_ACROSS_USERS, @@ -22448,7 +22582,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, MANAGE_DEVICE_POLICY_TIME, MANAGE_DEVICE_POLICY_VPN, - MANAGE_DEVICE_POLICY_WIPE_DATA + MANAGE_DEVICE_POLICY_WIPE_DATA, + MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES ); /** @@ -23477,10 +23612,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mInjector.binderWithCleanCallingIdentity(() -> { boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins(); - if (!canForceMigration && !shouldMigrateToDevicePolicyEngine()) { + if (!canForceMigration && !shouldMigrateV1ToDevicePolicyEngine()) { return false; } - return migratePoliciesToDevicePolicyEngine(); + return migrateV1PoliciesToDevicePolicyEngine(); }); } @@ -23503,14 +23638,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } - private boolean shouldMigrateToDevicePolicyEngine() { + private boolean shouldMigrateV1ToDevicePolicyEngine() { return mInjector.binderWithCleanCallingIdentity(() -> !mOwners.isMigratedToPolicyEngine()); } /** + * Migrates the initial set of policies to use policy engine. * @return {@code true} if policies were migrated successfully, {@code false} otherwise. */ - private boolean migratePoliciesToDevicePolicyEngine() { + private boolean migrateV1PoliciesToDevicePolicyEngine() { return mInjector.binderWithCleanCallingIdentity(() -> { try { synchronized (getLockObject()) { @@ -23537,6 +23673,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + /** + * Migrates the rest of policies to use policy engine. + */ + @GuardedBy("getLockObject()") + private void migratePoliciesToPolicyEngineLocked() { + maybeMigrateSecurityLoggingPolicyLocked(); + } + private void migrateAutoTimezonePolicy() { Slogf.i(LOG_TAG, "Skipping Migration of AUTO_TIMEZONE policy to device policy engine," + "as no way to identify if the value was set by the admin or the user."); @@ -23965,5 +24109,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } return adminOwnedSubscriptions; }); + + } + + @Override + public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) { + if (!devicePolicySizeTrackingEnabled()) { + return; + } + CallerIdentity caller = getCallerIdentity(callerPackageName); + enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), + caller.getUserId()); + + mDevicePolicyEngine.setMaxPolicyStorageLimit(storageLimit); + } + + @Override + public int getMaxPolicyStorageLimit(String callerPackageName) { + if (!devicePolicySizeTrackingEnabled()) { + return -1; + } + CallerIdentity caller = getCallerIdentity(callerPackageName); + enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), + caller.getUserId()); + + return mDevicePolicyEngine.getMaxPolicyStorageLimit(); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index bb275e45b55a..c5a98880ec84 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -616,6 +616,19 @@ class Owners { } } + void markSecurityLoggingMigrated() { + synchronized (mData) { + mData.mSecurityLoggingMigrated = true; + mData.writeDeviceOwner(); + } + } + + boolean isSecurityLoggingMigrated() { + synchronized (mData) { + return mData.mSecurityLoggingMigrated; + } + } + @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 37d4f95cac29..d9fef10ee41b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java @@ -16,6 +16,7 @@ package com.android.server.devicepolicy; import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT; +import static android.app.admin.flags.Flags.securityLogV2Enabled; import android.annotation.Nullable; import android.app.admin.SystemUpdateInfo; @@ -86,6 +87,7 @@ class OwnersData { private static final String ATTR_DEVICE_OWNER_TYPE_VALUE = "value"; private static final String ATTR_MIGRATED_TO_POLICY_ENGINE = "migratedToPolicyEngine"; + private static final String ATTR_SECURITY_LOG_MIGRATED = "securityLogMigrated"; // Internal state for the device owner package. OwnerInfo mDeviceOwner; @@ -113,6 +115,7 @@ class OwnersData { private final PolicyPathProvider mPathProvider; boolean mMigratedToPolicyEngine = false; + boolean mSecurityLoggingMigrated = false; OwnersData(PolicyPathProvider pathProvider) { mPathProvider = pathProvider; @@ -397,6 +400,9 @@ class OwnersData { out.startTag(null, TAG_POLICY_ENGINE_MIGRATION); out.attributeBoolean(null, ATTR_MIGRATED_TO_POLICY_ENGINE, mMigratedToPolicyEngine); + if (securityLogV2Enabled()) { + out.attributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, mSecurityLoggingMigrated); + } out.endTag(null, TAG_POLICY_ENGINE_MIGRATION); } @@ -457,6 +463,8 @@ class OwnersData { case TAG_POLICY_ENGINE_MIGRATION: mMigratedToPolicyEngine = parser.getAttributeBoolean( null, ATTR_MIGRATED_TO_POLICY_ENGINE, false); + mSecurityLoggingMigrated = securityLogV2Enabled() + && parser.getAttributeBoolean(null, ATTR_SECURITY_LOG_MIGRATED, false); break; default: Slog.e(TAG, "Unexpected tag: " + tag); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index b09908e608ba..3474db3c7c1f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -134,6 +134,13 @@ final class PolicyDefinition<V> { permissionName)); } + static PolicyDefinition<Boolean> SECURITY_LOGGING = new PolicyDefinition<>( + new NoArgsPolicyKey(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY), + TRUE_MORE_RESTRICTIVE, + POLICY_FLAG_GLOBAL_ONLY_POLICY, + PolicyEnforcerCallbacks::enforceSecurityLogging, + new BooleanPolicySerializer()); + static PolicyDefinition<LockTaskPolicy> LOCK_TASK = new PolicyDefinition<>( new NoArgsPolicyKey(DevicePolicyIdentifiers.LOCK_TASK_POLICY), new TopPriority<>(List.of( @@ -356,6 +363,8 @@ final class PolicyDefinition<V> { POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY, GENERIC_PERMISSION_GRANT); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.SECURITY_LOGGING_POLICY, + SECURITY_LOGGING); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.LOCK_TASK_POLICY, LOCK_TASK); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY, USER_CONTROLLED_DISABLED_PACKAGES); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 506dbe8c48c4..4aaefa670ea2 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.AppGlobals; import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IntentFilterPolicyKey; import android.app.admin.LockTaskPolicy; import android.app.admin.PackagePermissionPolicyKey; @@ -127,6 +128,14 @@ final class PolicyEnforcerCallbacks { } } + static boolean enforceSecurityLogging( + @Nullable Boolean value, @NonNull Context context, int userId, + @NonNull PolicyKey policyKey) { + final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class); + dpmi.enforceSecurityLoggingPolicy(Boolean.TRUE.equals(value)); + return true; + } + static boolean setLockTask( @Nullable LockTaskPolicy policy, @NonNull Context context, int userId) { List<String> packages = Collections.emptyList(); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java index 939a3dcfa32d..7a4454b11fce 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java @@ -101,6 +101,10 @@ class SecurityLogMonitor implements Runnable { /** Minimum time between forced fetch attempts. */ private static final long FORCE_FETCH_THROTTLE_NS = TimeUnit.SECONDS.toNanos(10); + /** + * Monitor thread is not null iff SecurityLogMonitor is running, i.e. started and not stopped. + * Pausing doesn't change it. + */ @GuardedBy("mLock") private Thread mMonitorThread = null; @GuardedBy("mLock") @@ -147,7 +151,6 @@ class SecurityLogMonitor implements Runnable { void start(int enabledUser) { Slog.i(TAG, "Starting security logging for user " + enabledUser); mEnabledUser = enabledUser; - SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED); mLock.lock(); try { if (mMonitorThread == null) { @@ -160,6 +163,11 @@ class SecurityLogMonitor implements Runnable { mMonitorThread = new Thread(this); mMonitorThread.start(); + + SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED); + Slog.i(TAG, "Security log monitor thread started"); + } else { + Slog.i(TAG, "Security log monitor thread is already running"); } } finally { mLock.unlock(); diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp index f15e533fee2b..bd86fe22f8d0 100644 --- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp +++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp @@ -32,6 +32,8 @@ android_test { "androidx.test.runner", "truth", "Harrier", + "frameworks-base-testutils", + "services.core", ], platform_apis: true, certificate: "platform", diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java index 4012d8e4af96..c615823f25aa 100644 --- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java +++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/AppEnumerationInternalTests.java @@ -16,32 +16,36 @@ package com.android.server.pm.test.appenumeration; -import static android.content.Context.MEDIA_PROJECTION_SERVICE; - import static com.android.compatibility.common.util.ShellUtils.runShellCommand; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.PendingIntent; import android.content.Context; import android.content.IntentSender; import android.content.pm.IPackageManager; import android.content.pm.ProviderInfo; -import android.media.projection.IMediaProjectionManager; import android.media.projection.MediaProjectionManager; import android.os.Process; -import android.os.ServiceManager; import android.os.UserHandle; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.util.test.LocalServiceKeeperRule; +import com.android.server.media.projection.MediaProjectionManagerService; + import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import java.util.ArrayList; import java.util.List; @@ -73,9 +77,17 @@ public class AppEnumerationInternalTests { private IPackageManager mIPackageManager; + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + + @Mock private ActivityManagerInternal mActivityManagerInternal; + @Before public void setup() { + initMocks(this); mIPackageManager = AppGlobals.getPackageManager(); + mLocalServiceKeeperRule.overrideLocalService(ActivityManagerInternal.class, + mActivityManagerInternal); } @After @@ -169,11 +181,11 @@ public class AppEnumerationInternalTests { public void mediaProjectionManager_createProjection_canSeeForceQueryable() throws Exception { installPackage(SHARED_USER_APK_PATH, true /* forceQueryable */); - final IMediaProjectionManager mediaProjectionManager = - IMediaProjectionManager.Stub.asInterface( - ServiceManager.getService(MEDIA_PROJECTION_SERVICE)); + final Context context = InstrumentationRegistry.getInstrumentation().getContext(); + final MediaProjectionManagerService mediaProjectionManager = + new MediaProjectionManagerService(context); - assertThat(mediaProjectionManager.createProjection(0 /* uid */, TARGET_SHARED_USER, + assertThat(mediaProjectionManager.createProjectionInternal(0 /* uid */, TARGET_SHARED_USER, MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */)) .isNotNull(); } @@ -181,12 +193,13 @@ public class AppEnumerationInternalTests { @Test public void mediaProjectionManager_createProjection_cannotSeeTarget() { installPackage(SHARED_USER_APK_PATH, false /* forceQueryable */); - final IMediaProjectionManager mediaProjectionManager = - IMediaProjectionManager.Stub.asInterface( - ServiceManager.getService(MEDIA_PROJECTION_SERVICE)); + final Context context = InstrumentationRegistry.getInstrumentation().getContext(); + final MediaProjectionManagerService mediaProjectionManager = + new MediaProjectionManagerService(context); Assert.assertThrows(IllegalArgumentException.class, - () -> mediaProjectionManager.createProjection(0 /* uid */, TARGET_SHARED_USER, + () -> mediaProjectionManager.createProjectionInternal(0 /* uid */, + TARGET_SHARED_USER, MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */)); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java index 6986cab72f56..e59b5ea027ed 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java @@ -270,6 +270,9 @@ public abstract class BaseLockSettingsServiceTests { } protected void setSecureFrpMode(boolean secure) { + if (android.security.Flags.frpEnforcement()) { + mStorage.setTestFactoryResetProtectionState(secure); + } Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.SECURE_FRP_MODE, secure ? 1 : 0, UserHandle.USER_SYSTEM); } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java index ee076c6bcf4b..296d2cba83dd 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java @@ -21,12 +21,14 @@ import static org.mockito.Mockito.mock; import android.app.IActivityManager; import android.app.admin.DeviceStateCache; import android.content.Context; +import android.content.Intent; import android.content.pm.UserInfo; import android.hardware.authsecret.IAuthSecret; import android.os.Handler; import android.os.Parcel; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.os.storage.IStorageManager; import android.security.KeyStore; import android.security.keystore.KeyPermanentlyInvalidatedException; @@ -41,6 +43,9 @@ import com.android.server.pm.UserManagerInternal; import java.io.FileNotFoundException; public class LockSettingsServiceTestable extends LockSettingsService { + private Intent mSavedFrpNotificationIntent = null; + private UserHandle mSavedFrpNotificationUserHandle = null; + private String mSavedFrpNotificationPermission = null; public static class MockInjector extends LockSettingsService.Injector { @@ -218,4 +223,29 @@ public class LockSettingsServiceTestable extends LockSettingsService { mAuthSecret = null; } } + + @Override + void sendBroadcast(Intent intent, UserHandle userHandle, String permission) { + mSavedFrpNotificationIntent = intent; + mSavedFrpNotificationUserHandle = userHandle; + mSavedFrpNotificationPermission = permission; + } + + String getSavedFrpNotificationPermission() { + return mSavedFrpNotificationPermission; + } + + UserHandle getSavedFrpNotificationUserHandle() { + return mSavedFrpNotificationUserHandle; + } + + Intent getSavedFrpNotificationIntent() { + return mSavedFrpNotificationIntent; + } + + void clearRecordedFrpNotificationData() { + mSavedFrpNotificationIntent = null; + mSavedFrpNotificationPermission = null; + mSavedFrpNotificationUserHandle = null; + } } diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java index 705359708bc7..4b22652a3f21 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java @@ -16,6 +16,7 @@ package com.android.server.locksettings; +import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION; import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; @@ -39,7 +40,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.PropertyInvalidatedCache; +import android.content.Intent; import android.os.RemoteException; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.service.gatekeeper.GateKeeperResponse; @@ -239,6 +242,12 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + public void testSetLockCredential_forPrimaryUser_sendsFrpNotification() throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + checkRecordedFrpNotificationIntent(); + } + + @Test public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception { setCredential(PRIMARY_USER_ID, newPassword("password")); verify(mRecoverableKeyStoreManager) @@ -323,6 +332,15 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @Test + public void testClearLockCredential_sendsFrpNotification() throws Exception { + setCredential(PRIMARY_USER_ID, newPassword("password")); + checkRecordedFrpNotificationIntent(); + mService.clearRecordedFrpNotificationData(); + clearCredential(PRIMARY_USER_ID, newPassword("password")); + checkRecordedFrpNotificationIntent(); + } + + @Test public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials() throws Exception { final LockscreenCredential parentPassword = newPassword("parentPassword"); @@ -519,6 +537,23 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { mService.setString(null, "value", 0); } + private void checkRecordedFrpNotificationIntent() { + if (android.security.Flags.frpEnforcement()) { + Intent savedNotificationIntent = mService.getSavedFrpNotificationIntent(); + assertNotNull(savedNotificationIntent); + UserHandle userHandle = mService.getSavedFrpNotificationUserHandle(); + assertEquals(userHandle, + UserHandle.of(mInjector.getUserManagerInternal().getMainUserId())); + + String permission = mService.getSavedFrpNotificationPermission(); + assertEquals(CONFIGURE_FACTORY_RESET_PROTECTION, permission); + } else { + assertNull(mService.getSavedFrpNotificationIntent()); + assertNull(mService.getSavedFrpNotificationUserHandle()); + assertNull(mService.getSavedFrpNotificationPermission()); + } + } + private void checkPasswordHistoryLength(int userId, int expectedLen) { String history = mService.getString(LockPatternUtils.PASSWORD_HISTORY_KEY, "", userId); String[] hashes = TextUtils.split(history, LockPatternUtils.PASSWORD_HISTORY_DELIMITER); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java index fa3c7a4c4769..c01d0f644983 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java @@ -35,6 +35,7 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { public final File mStorageDir; public PersistentDataBlockManagerInternal mPersistentDataBlockManager; private byte[] mPersistentData; + private boolean mIsFactoryResetProtectionActive = false; public LockSettingsStorageTestable(Context context, File storageDir) { super(context); @@ -63,6 +64,10 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { }).when(mPersistentDataBlockManager).getFrpCredentialHandle(); } + void setTestFactoryResetProtectionState(boolean active) { + mIsFactoryResetProtectionActive = active; + } + @Override File getChildProfileLockFile(int userId) { return remapToStorageDir(super.getChildProfileLockFile(userId)); @@ -101,4 +106,9 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { mappedPath.getParentFile().mkdirs(); return mappedPath; } + + @Override + public boolean isFactoryResetProtectionActive() { + return mIsFactoryResetProtectionActive; + } } 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 abd3abee82fb..aefa6de9184a 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 @@ -75,12 +75,12 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.server.LocalServices; +import com.android.internal.util.test.LocalServiceKeeperRule; import com.android.server.testutils.OffsettableClock; import com.android.server.wm.WindowManagerInternal; -import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -164,15 +164,17 @@ public class MediaProjectionManagerServiceTest { @Captor private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); when(mWatcherCallback.asBinder()).thenReturn(new Binder()); - LocalServices.removeServiceForTest(ActivityManagerInternal.class); - LocalServices.addService(ActivityManagerInternal.class, mAmInternal); - LocalServices.removeServiceForTest(WindowManagerInternal.class); - LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal); + mLocalServiceKeeperRule.overrideLocalService(ActivityManagerInternal.class, mAmInternal); + mLocalServiceKeeperRule.overrideLocalService(WindowManagerInternal.class, + mWindowManagerInternal); mContext = spy(new ContextWrapper( InstrumentationRegistry.getInstrumentation().getTargetContext())); @@ -187,12 +189,6 @@ public class MediaProjectionManagerServiceTest { mService = new MediaProjectionManagerService(mContext); } - @After - public void tearDown() { - LocalServices.removeServiceForTest(ActivityManagerInternal.class); - LocalServices.removeServiceForTest(WindowManagerInternal.class); - } - @Test public void testGetActiveProjectionInfoInternal() throws NameNotFoundException { assertThat(mService.getActiveProjectionInfo()).isNull(); @@ -388,16 +384,16 @@ public class MediaProjectionManagerServiceTest { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions( service); // No starts yet, and not timed out yet - so still valid. - assertThat(projection.isValid()).isTrue(); + assertThat(projection.isValidInternal()).isTrue(); // Only one start - so still valid. projection.start(mIMediaProjectionCallback); - assertThat(projection.isValid()).isTrue(); + assertThat(projection.isValidInternal()).isTrue(); // Second start - technically allowed to start again, without stopping in between. // Token should no longer be valid. projection.start(mIMediaProjectionCallback); - assertThat(projection.isValid()).isFalse(); + assertThat(projection.isValidInternal()).isFalse(); } @Test @@ -407,17 +403,17 @@ public class MediaProjectionManagerServiceTest { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions( service); // No starts yet, and not timed out yet - so still valid. - assertThat(projection.isValid()).isTrue(); + assertThat(projection.isValidInternal()).isTrue(); // Only one start - so still valid. projection.start(mIMediaProjectionCallback); - assertThat(projection.isValid()).isTrue(); + assertThat(projection.isValidInternal()).isTrue(); projection.stop(); // Second start - so not valid. projection.start(mIMediaProjectionCallback); - assertThat(projection.isValid()).isFalse(); + assertThat(projection.isValidInternal()).isFalse(); } @Test @@ -442,7 +438,7 @@ public class MediaProjectionManagerServiceTest { mClock.fastForward(projection.mDefaultTimeoutMs + 10); // Immediate timeout - so no longer valid. - assertThat(projection.isValid()).isFalse(); + assertThat(projection.isValidInternal()).isFalse(); } @Test @@ -452,10 +448,10 @@ public class MediaProjectionManagerServiceTest { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions( service); // Simulate MediaProjection#createVirtualDisplay being invoked previously. - projection.notifyVirtualDisplayCreated(10); + projection.notifyVirtualDisplayCreatedInternal(10); // Trying to re-use token on another MediaProjection#createVirtualDisplay - no longer valid. - assertThat(projection.isValid()).isFalse(); + assertThat(projection.isValidInternal()).isFalse(); } // TODO(269273190): Test flag using compat annotations instead. @@ -471,7 +467,7 @@ public class MediaProjectionManagerServiceTest { // Second start - so not valid. projection.start(mIMediaProjectionCallback); - assertThrows(SecurityException.class, projection::isValid); + assertThrows(SecurityException.class, projection::isValidInternal); } // TODO(269273190): Test flag using compat annotations instead. @@ -488,7 +484,7 @@ public class MediaProjectionManagerServiceTest { // Second start - so not valid. projection.start(mIMediaProjectionCallback); - assertThat(projection.isValid()).isFalse(); + assertThat(projection.isValidInternal()).isFalse(); } @Test @@ -627,7 +623,7 @@ public class MediaProjectionManagerServiceTest { mService.setUserReviewGrantedConsentResult(RECORD_CONTENT_DISPLAY, projection); // Virtual Display is finally created. - projection.notifyVirtualDisplayCreated(10); + projection.notifyVirtualDisplayCreatedInternal(10); verifySetSessionWithContent(ContentRecordingSession.RECORD_CONTENT_DISPLAY); } @@ -730,7 +726,7 @@ public class MediaProjectionManagerServiceTest { throws Exception { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); projection.start(mIMediaProjectionCallback); - projection.notifyVirtualDisplayCreated(10); + projection.notifyVirtualDisplayCreatedInternal(10); // Waiting for user to review consent. assertThat(mService.isCurrentProjection(projection)).isTrue(); doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( @@ -785,9 +781,9 @@ public class MediaProjectionManagerServiceTest { @RecordContent int recordedContent) throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); - projection.setLaunchCookie(new LaunchCookie()); + projection.setLaunchCookieInternal(new LaunchCookie()); projection.start(mIMediaProjectionCallback); - projection.notifyVirtualDisplayCreated(10); + projection.notifyVirtualDisplayCreatedInternal(10); // Waiting for user to review consent. doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( any(ContentRecordingSession.class)); @@ -808,7 +804,7 @@ public class MediaProjectionManagerServiceTest { throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); projection.start(mIMediaProjectionCallback); - projection.notifyVirtualDisplayCreated(10); + projection.notifyVirtualDisplayCreatedInternal(10); // Waiting for user to review consent. doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( eq(mWaitingDisplaySession)); @@ -826,7 +822,7 @@ public class MediaProjectionManagerServiceTest { public void testSetUserReviewGrantedConsentResult_displayMirroring_noPriorSession() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); - projection.setLaunchCookie(new LaunchCookie()); + projection.setLaunchCookieInternal(new LaunchCookie()); projection.start(mIMediaProjectionCallback); // Skip setting the prior session details. @@ -845,7 +841,7 @@ public class MediaProjectionManagerServiceTest { public void testSetUserReviewGrantedConsentResult_displayMirroring_sessionNotWaiting() throws NameNotFoundException { MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); - projection.setLaunchCookie(new LaunchCookie()); + projection.setLaunchCookieInternal(new LaunchCookie()); projection.start(mIMediaProjectionCallback); // Session is not waiting for user's consent. doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index d50affba1ea1..a0f2395f5203 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static android.content.pm.ShortcutInfo.DISABLED_REASON_BACKUP_NOT_SUPPORTED; import static android.content.pm.ShortcutInfo.DISABLED_REASON_NOT_DISABLED; import static android.content.pm.ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH; -import static android.content.pm.ShortcutInfo.DISABLED_REASON_VERSION_LOWER; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyOrNull; import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyStringOrNull; @@ -78,7 +77,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; -import android.content.pm.LauncherApps.PinItemRequest; import android.content.pm.LauncherApps.ShortcutQuery; import android.content.pm.PackageInfo; import android.content.pm.ShortcutInfo; @@ -4844,7 +4842,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { doReturn(expected != DISABLED_REASON_SIGNATURE_MISMATCH).when( mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class), anyString()); - assertEquals(expected, spi.canRestoreTo(mService, pi, anyVersionOk)); + assertEquals(expected, spi.canRestoreTo(mService, pi, true)); } public void testCanRestoreTo() { @@ -4872,7 +4870,6 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x"); checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x", "y"); checkCanRestoreTo(DISABLED_REASON_SIGNATURE_MISMATCH, spi1, false, 10, true, "x"); - checkCanRestoreTo(DISABLED_REASON_VERSION_LOWER, spi1, false, 9, true, "sig1"); // Any version okay. checkCanRestoreTo(DISABLED_REASON_NOT_DISABLED, spi1, true, 9, true, "sig1"); @@ -5983,14 +5980,6 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void testBackupAndRestore_publisherLowerVersion() { - prepareForBackupTest(); - - addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version - - checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_VERSION_LOWER); - } - public void testBackupAndRestore_publisherWrongSignature() { prepareForBackupTest(); @@ -6626,252 +6615,6 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - - /** - * Restored to a lower version with no manifest shortcuts. All shortcuts are now invisible, - * and all calls from the publisher should ignore them. - */ - public void testBackupAndRestore_disabledShortcutsAreIgnored() { - // Publish two manifest shortcuts. - addManifestShortcutResource( - new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), - R.xml.shortcut_5_altalt); - updatePackageVersion(CALLING_PACKAGE_1, 1); - mService.mPackageMonitor.onReceive(mServiceContext, - genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); - - runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - assertTrue(mManager.setDynamicShortcuts(list( - makeShortcutWithShortLabel("s1", "original-title"), - makeShortcut("s2"), makeShortcut("s3")))); - }); - - // Pin from launcher 1. - runWithCaller(LAUNCHER_1, USER_0, () -> { - mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, - list("ms1", "ms2", "ms3", "ms4", "s1", "s2"), HANDLE_USER_0); - }); - - doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe( - any(byte[].class), anyString()); - - backupAndRestore(); - - // Lower the version and remove the manifest shortcuts. - addManifestShortcutResource( - new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), - R.xml.shortcut_0); - addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 0); // Lower version - - // When re-installing the app, the manifest shortcut should be re-published. - mService.mPackageMonitor.onReceive(mServiceContext, - genPackageAddIntent(CALLING_PACKAGE_1, USER_0)); - mService.mPackageMonitor.onReceive(mServiceContext, - genPackageAddIntent(LAUNCHER_1, USER_0)); - - // No shortcuts should be visible to the publisher. - runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - assertWith(getCallerVisibleShortcuts()) - .isEmpty(); - }); - - final Runnable checkAllDisabledForLauncher = () -> { - runWithCaller(LAUNCHER_1, USER_0, () -> { - assertWith(getShortcutAsLauncher(USER_0)) - .areAllPinned() - .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2") - .areAllDisabled() - .areAllWithDisabledReason(ShortcutInfo.DISABLED_REASON_VERSION_LOWER) - - .forShortcutWithId("s1", si -> { - assertEquals("original-title", si.getShortLabel()); - }) - .forShortcutWithId("ms1", si -> { - assertEquals("string-com.android.test.1-user:0-res:" - + R.string.shortcut_title1 + "/en" - , si.getShortLabel()); - }) - .forShortcutWithId("ms2", si -> { - assertEquals("string-com.android.test.1-user:0-res:" - + R.string.shortcut_title2 + "/en" - , si.getShortLabel()); - }) - .forShortcutWithId("ms3", si -> { - assertEquals("string-com.android.test.1-user:0-res:" - + R.string.shortcut_title1 + "/en" - , si.getShortLabel()); - assertEquals("string-com.android.test.1-user:0-res:" - + R.string.shortcut_title2 + "/en" - , si.getLongLabel()); - }) - .forShortcutWithId("ms4", si -> { - assertEquals("string-com.android.test.1-user:0-res:" - + R.string.shortcut_title2 + "/en" - , si.getShortLabel()); - assertEquals("string-com.android.test.1-user:0-res:" - + R.string.shortcut_title2 + "/en" - , si.getLongLabel()); - }); - }); - }; - - checkAllDisabledForLauncher.run(); - - runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { - - makeCallerForeground(); // CALLING_PACKAGE_1 is now in the foreground. - - // All changing API calls should be ignored. - - getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2")); - checkAllDisabledForLauncher.run(); - - getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2")); - checkAllDisabledForLauncher.run(); - - getManager().enableShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2")); - checkAllDisabledForLauncher.run(); - - getManager().removeAllDynamicShortcuts(); - getManager().removeDynamicShortcuts(list("ms1", "ms2", "ms3", "ms4", "s1", "s2")); - checkAllDisabledForLauncher.run(); - - getManager().updateShortcuts(list(makeShortcutWithShortLabel("s1", "new-title"))); - checkAllDisabledForLauncher.run(); - - - // Add a shortcut -- even though ms1 was immutable, it will succeed. - assertTrue(getManager().addDynamicShortcuts(list( - makeShortcutWithShortLabel("ms1", "original-title")))); - - runWithCaller(LAUNCHER_1, USER_0, () -> { - assertWith(getShortcutAsLauncher(USER_0)) - .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2") - - .selectByIds("ms1") - .areAllEnabled() - .areAllDynamic() - .areAllPinned() - .forAllShortcuts(si -> { - assertEquals("original-title", si.getShortLabel()); - }) - - // The rest still exist and disabled. - .revertToOriginalList() - .selectByIds("ms2", "ms3", "ms4", "s1", "s2") - .areAllDisabled() - .areAllPinned() - ; - }); - - assertTrue(getManager().setDynamicShortcuts(list( - makeShortcutWithShortLabel("ms2", "new-title-2")))); - - runWithCaller(LAUNCHER_1, USER_0, () -> { - assertWith(getShortcutAsLauncher(USER_0)) - .haveIds("ms1", "ms2", "ms3", "ms4", "s1", "s2") - - .selectByIds("ms1") - .areAllEnabled() - .areAllNotDynamic() // ms1 was not in the list, so no longer dynamic. - .areAllPinned() - .areAllMutable() - .forAllShortcuts(si -> { - assertEquals("original-title", si.getShortLabel()); - }) - - .revertToOriginalList() - .selectByIds("ms2") - .areAllEnabled() - .areAllDynamic() - .areAllPinned() - .areAllMutable() - .forAllShortcuts(si -> { - assertEquals("new-title-2", si.getShortLabel()); - }) - - // The rest still exist and disabled. - .revertToOriginalList() - .selectByIds("ms3", "ms4", "s1", "s2") - .areAllDisabled() - .areAllPinned() - ; - }); - - // Prepare for requestPinShortcut(). - setDefaultLauncher(USER_0, LAUNCHER_1); - mPinConfirmActivityFetcher = (packageName, userId) -> - new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS); - - mManager.requestPinShortcut( - makeShortcutWithShortLabel("ms3", "new-title-3"), - /*PendingIntent=*/ null); - - // Note this was pinned, so it'll be accepted right away. - runWithCaller(LAUNCHER_1, USER_0, () -> { - assertWith(getShortcutAsLauncher(USER_0)) - .selectByIds("ms3") - .areAllEnabled() - .areAllNotDynamic() - .areAllPinned() - .areAllMutable() - .forAllShortcuts(si -> { - assertEquals("new-title-3", si.getShortLabel()); - // The new one replaces the old manifest shortcut, so the long label - // should be gone now. - assertNull(si.getLongLabel()); - }); - }); - - // Now, change the launcher to launcher2, and request pin again. - setDefaultLauncher(USER_0, LAUNCHER_2); - - reset(mServiceContext); - - assertTrue(mManager.isRequestPinShortcutSupported()); - mManager.requestPinShortcut( - makeShortcutWithShortLabel("ms4", "new-title-4"), - /*PendingIntent=*/ null); - - // Initially there should be no pinned shortcuts for L2. - runWithCaller(LAUNCHER_2, USER_0, () -> { - assertWith(getShortcutAsLauncher(USER_0)) - .selectPinned() - .isEmpty(); - - final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class); - - verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0)); - - assertEquals(LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT, - intent.getValue().getAction()); - assertEquals(LAUNCHER_2, intent.getValue().getComponent().getPackageName()); - - // Check the request object. - final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue()); - - assertNotNull(request); - assertEquals(PinItemRequest.REQUEST_TYPE_SHORTCUT, request.getRequestType()); - - assertWith(request.getShortcutInfo()) - .haveIds("ms4") - .areAllOrphan() - .forAllShortcuts(si -> { - assertEquals("new-title-4", si.getShortLabel()); - // The new one replaces the old manifest shortcut, so the long label - // should be gone now. - assertNull(si.getLongLabel()); - }); - assertTrue(request.accept()); - - assertWith(getShortcutAsLauncher(USER_0)) - .selectPinned() - .haveIds("ms4") - .areAllEnabled(); - }); - }); - } - /** * Test for restoring the pre-P backup format. */ diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index 5eb76e352ea2..f0779144ad18 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -22,6 +22,9 @@ import static android.app.Notification.FLAG_CAN_COLORIZE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; +import static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.Notification.VISIBILITY_PUBLIC; +import static android.app.Notification.VISIBILITY_SECRET; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static com.android.server.notification.GroupHelper.BASE_FLAGS; @@ -81,6 +84,8 @@ public class GroupHelperTest extends UiServiceTestCase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + private final int DEFAULT_VISIBILITY = VISIBILITY_PRIVATE; + private @Mock GroupHelper.Callback mCallback; private @Mock PackageManager mPackageManager; @@ -127,7 +132,7 @@ public class GroupHelperTest extends UiServiceTestCase { } private NotificationAttributes getNotificationAttributes(int flags) { - return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT); + return new NotificationAttributes(flags, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY); } @Test @@ -704,7 +709,8 @@ public class GroupHelperTest extends UiServiceTestCase { final Icon icon = mock(Icon.class); when(icon.sameAs(icon)).thenReturn(true); final int iconColor = Color.BLUE; - final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor); + final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor, + DEFAULT_VISIBILITY); // Add notifications with same icon and color for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -744,7 +750,7 @@ public class GroupHelperTest extends UiServiceTestCase { doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg)); final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS, - initialIcon, initialIconColor); + initialIcon, initialIconColor, DEFAULT_VISIBILITY); // Add notifications with same icon and color for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -769,7 +775,42 @@ public class GroupHelperTest extends UiServiceTestCase { // Summary should be updated to the default color and the icon to the monochrome icon NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon, - COLOR_DEFAULT); + COLOR_DEFAULT, DEFAULT_VISIBILITY); + verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr)); + } + + @Test + @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) + public void testAddSummary_diffVisibility() { + final String pkg = "package"; + final Icon icon = mock(Icon.class); + when(icon.sameAs(icon)).thenReturn(true); + final int iconColor = Color.BLUE; + final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor, + VISIBILITY_PRIVATE); + + // Add notifications with same icon and color and default visibility (private) + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null, + icon, iconColor); + mGroupHelper.onNotificationPosted(sbn, false); + } + // Check that the summary has private visibility + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(attr)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + + // After auto-grouping, add new notification with public visibility + StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT, + String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor); + sbn.getNotification().visibility = VISIBILITY_PUBLIC; + mGroupHelper.onNotificationPosted(sbn, true); + + // Check that the summary visibility was updated + NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor, + VISIBILITY_PUBLIC); verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr)); } @@ -781,7 +822,7 @@ public class GroupHelperTest extends UiServiceTestCase { when(initialIcon.sameAs(initialIcon)).thenReturn(true); final int initialIconColor = Color.BLUE; final NotificationAttributes initialAttr = new NotificationAttributes( - GroupHelper.FLAG_INVALID, initialIcon, initialIconColor); + GroupHelper.FLAG_INVALID, initialIcon, initialIconColor, DEFAULT_VISIBILITY); // Add AUTOGROUP_AT_COUNT-1 notifications with same icon and color ArrayList<StatusBarNotification> notifications = new ArrayList<>(); @@ -817,11 +858,12 @@ public class GroupHelperTest extends UiServiceTestCase { // Create notifications with the same icon List<NotificationAttributes> childrenAttr = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { - childrenAttr.add(new NotificationAttributes(0, icon, COLOR_DEFAULT)); + childrenAttr.add(new NotificationAttributes(0, icon, COLOR_DEFAULT, + DEFAULT_VISIBILITY)); } //Check that the generated summary icon is the same as the child notifications' - Icon summaryIcon = mGroupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon; + Icon summaryIcon = mGroupHelper.getAutobundledSummaryAttributes(pkg, childrenAttr).icon; assertThat(summaryIcon).isEqualTo(icon); } @@ -837,11 +879,12 @@ public class GroupHelperTest extends UiServiceTestCase { // Create notifications with different icons List<NotificationAttributes> childrenAttr = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { - childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), COLOR_DEFAULT)); + childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), COLOR_DEFAULT, + DEFAULT_VISIBILITY)); } // Check that the generated summary icon is the monochrome icon - Icon summaryIcon = groupHelper.getAutobundledSummaryIconAndColor(pkg, childrenAttr).icon; + Icon summaryIcon = groupHelper.getAutobundledSummaryAttributes(pkg, childrenAttr).icon; assertThat(summaryIcon).isEqualTo(monochromeIcon); } @@ -853,11 +896,12 @@ public class GroupHelperTest extends UiServiceTestCase { // Create notifications with the same icon color List<NotificationAttributes> childrenAttr = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { - childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor)); + childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor, + DEFAULT_VISIBILITY)); } // Check that the generated summary icon color is the same as the child notifications' - int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg, + int summaryIconColor = mGroupHelper.getAutobundledSummaryAttributes(pkg, childrenAttr).iconColor; assertThat(summaryIconColor).isEqualTo(iconColor); } @@ -869,17 +913,62 @@ public class GroupHelperTest extends UiServiceTestCase { // Create notifications with different icon colors List<NotificationAttributes> childrenAttr = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { - childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), i)); + childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), i, + DEFAULT_VISIBILITY)); } // Check that the generated summary icon color is the default color - int summaryIconColor = mGroupHelper.getAutobundledSummaryIconAndColor(pkg, + int summaryIconColor = mGroupHelper.getAutobundledSummaryAttributes(pkg, childrenAttr).iconColor; assertThat(summaryIconColor).isEqualTo(Notification.COLOR_DEFAULT); } @Test @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) + public void testAutobundledSummaryVisibility_hasPublicChildren() { + final String pkg = "package"; + final int iconColor = Color.BLUE; + // Create notifications with private and public visibility + List<NotificationAttributes> childrenAttr = new ArrayList<>(); + childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor, + VISIBILITY_PUBLIC)); + for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { + childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor, + VISIBILITY_PRIVATE)); + } + + // Check that the generated summary visibility is public + int summaryVisibility = mGroupHelper.getAutobundledSummaryAttributes(pkg, + childrenAttr).visibility; + assertThat(summaryVisibility).isEqualTo(VISIBILITY_PUBLIC); + } + + @Test + @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) + public void testAutobundledSummaryVisibility_noPublicChildren() { + final String pkg = "package"; + final int iconColor = Color.BLUE; + int visibility = VISIBILITY_PRIVATE; + // Create notifications with either private or secret visibility + List<NotificationAttributes> childrenAttr = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + if (i % 2 == 0) { + visibility = VISIBILITY_PRIVATE; + } else { + visibility = VISIBILITY_SECRET; + } + childrenAttr.add(new NotificationAttributes(0, mock(Icon.class), iconColor, + visibility)); + } + + // Check that the generated summary visibility is private + int summaryVisibility = mGroupHelper.getAutobundledSummaryAttributes(pkg, + childrenAttr).visibility; + assertThat(summaryVisibility).isEqualTo(VISIBILITY_PRIVATE); + } + + @Test + @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) public void testMonochromeAppIcon_adaptiveIconExists() throws Exception { final String pkg = "testPackage"; final int monochromeIconResId = 1234; 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 6aacfd706adc..94d24a91bbc9 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -37,6 +37,7 @@ import static android.app.Notification.FLAG_NO_CLEAR; import static android.app.Notification.FLAG_ONGOING_EVENT; import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.Notification.FLAG_USER_INITIATED_JOB; +import static android.app.Notification.VISIBILITY_PRIVATE; import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE; import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; @@ -2338,7 +2339,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.updateAutobundledSummaryLocked(0, "pkg", new NotificationAttributes(GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, - mock(Icon.class), 0), false); + mock(Icon.class), 0, VISIBILITY_PRIVATE), false); waitForIdle(); assertTrue(summary.getSbn().isOngoing()); @@ -2357,7 +2358,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.updateAutobundledSummaryLocked(0, "pkg", new NotificationAttributes(GroupHelper.BASE_FLAGS, - mock(Icon.class), 0), false); + mock(Icon.class), 0, VISIBILITY_PRIVATE), false); waitForIdle(); assertFalse(summary.getSbn().isOngoing()); @@ -3479,7 +3480,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true); NotificationRecord r = mService.createAutoGroupSummary(temp.getUserId(), - temp.getSbn().getPackageName(), temp.getKey(), 0, mock(Icon.class), 0); + temp.getSbn().getPackageName(), temp.getKey(), 0, mock(Icon.class), 0, + VISIBILITY_PRIVATE); assertThat(r.isImportanceFixed()).isTrue(); } @@ -12215,9 +12217,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // grouphelper is a mock here, so make the calls it would make // add summary - mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(), - nr1.getSbn().getPackageName(), nr1.getKey(), - GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0)); + mService.addNotification( + mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), + nr1.getKey(), GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, mock(Icon.class), 0, + VISIBILITY_PRIVATE)); // cancel both children mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(), @@ -12246,7 +12249,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification(nr1); mService.addNotification( mService.createAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), - nr1.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0)); + nr1.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0, VISIBILITY_PRIVATE)); // add notifications + summary for USER_ALL NotificationRecord nr0_all = @@ -12259,7 +12262,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.addNotification( mService.createAutoGroupSummary(nr0_all.getUserId(), nr0_all.getSbn().getPackageName(), - nr0_all.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0)); + nr0_all.getKey(), GroupHelper.BASE_FLAGS, mock(Icon.class), 0, VISIBILITY_PRIVATE)); // cancel both children for USER_ALL mBinderService.cancelNotificationWithTag(PKG, PKG, nr0_all.getSbn().getTag(), 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 f9ba33b526a9..6e5c180de347 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -649,6 +649,74 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testTotalSilence_consolidatedPolicyDisallowsAll() { + // Start with zen mode off just to make sure global/manual mode isn't doing anything. + mZenModeHelper.mZenMode = ZEN_MODE_OFF; + + // Create a zen rule that calls for total silence via zen mode, but does not specify any + // particular policy. This confirms that the application of the policy is based only on the + // actual zen mode setting. + AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID) + .setInterruptionFilter(INTERRUPTION_FILTER_NONE) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID); + + // Enable rule + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(azr.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + Process.SYSTEM_UID); + + // Confirm that the consolidated policy doesn't allow anything + NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy(); + assertThat(policy.allowAlarms()).isFalse(); + assertThat(policy.allowMedia()).isFalse(); + assertThat(policy.allowCalls()).isFalse(); + assertThat(policy.allowMessages()).isFalse(); + assertThat(policy.allowConversations()).isFalse(); + assertThat(policy.allowEvents()).isFalse(); + assertThat(policy.allowReminders()).isFalse(); + assertThat(policy.allowRepeatCallers()).isFalse(); + assertThat(policy.allowPriorityChannels()).isFalse(); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testAlarmsOnly_consolidatedPolicyOnlyAllowsAlarmsAndMedia() { + // Start with zen mode off just to make sure global/manual mode isn't doing anything. + mZenModeHelper.mZenMode = ZEN_MODE_OFF; + + // Create a zen rule that calls for alarms only via zen mode, but does not specify any + // particular policy. This confirms that the application of the policy is based only on the + // actual zen mode setting. + AutomaticZenRule azr = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID) + .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS) + .build(); + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + azr, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reason", Process.SYSTEM_UID); + + // Enable rule + mZenModeHelper.setAutomaticZenRuleState(ruleId, + new Condition(azr.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + Process.SYSTEM_UID); + + // Confirm that the consolidated policy allows only alarms and media and nothing else + NotificationManager.Policy policy = mZenModeHelper.getConsolidatedNotificationPolicy(); + assertThat(policy.allowAlarms()).isTrue(); + assertThat(policy.allowMedia()).isTrue(); + assertThat(policy.allowCalls()).isFalse(); + assertThat(policy.allowMessages()).isFalse(); + assertThat(policy.allowConversations()).isFalse(); + assertThat(policy.allowEvents()).isFalse(); + assertThat(policy.allowReminders()).isFalse(); + assertThat(policy.allowRepeatCallers()).isFalse(); + assertThat(policy.allowPriorityChannels()).isFalse(); + } + + @Test public void testZenUpgradeNotification() { /** * Commit a485ec65b5ba947d69158ad90905abf3310655cf disabled DND status change diff --git a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java deleted file mode 100644 index 159c760992c6..000000000000 --- a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.voiceinteraction; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; - -import static com.google.common.truth.Truth.assertThat; - -import static org.junit.Assert.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -import android.Manifest; -import android.app.ActivityManagerInternal; -import android.app.AppOpsManager; -import android.app.role.RoleManager; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.os.PermissionEnforcer; -import android.os.Process; -import android.os.test.FakePermissionEnforcer; -import android.platform.test.annotations.Presubmit; - -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.modules.utils.testing.ExtendedMockitoRule; -import com.android.server.LocalServices; -import com.android.server.pm.UserManagerInternal; -import com.android.server.pm.permission.LegacyPermissionManagerInternal; -import com.android.server.pm.permission.PermissionManagerServiceInternal; -import com.android.server.wm.ActivityTaskManagerInternal; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.quality.Strictness; - -@SmallTest -@Presubmit -@RunWith(AndroidJUnit4.class) -public class SetSandboxedTrainingDataAllowedTest { - - @Captor private ArgumentCaptor<Integer> mOpIdCaptor, mUidCaptor, mOpModeCaptor; - - @Mock - private AppOpsManager mAppOpsManager; - - @Mock - private VoiceInteractionManagerServiceImpl mVoiceInteractionManagerServiceImpl; - - private FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer(); - - private Context mContext; - - private VoiceInteractionManagerService mVoiceInteractionManagerService; - private VoiceInteractionManagerService.VoiceInteractionManagerServiceStub - mVoiceInteractionManagerServiceStub; - - private ApplicationInfo mApplicationInfo = new ApplicationInfo(); - - @Rule - public final ExtendedMockitoRule mExtendedMockitoRule = - new ExtendedMockitoRule.Builder(this) - .setStrictness(Strictness.WARN) - .mockStatic(LocalServices.class) - .mockStatic(PermissionEnforcer.class) - .build(); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mContext = spy(ApplicationProvider.getApplicationContext()); - - doReturn(mPermissionEnforcer).when(() -> PermissionEnforcer.fromContext(any())); - doReturn(mock(PermissionManagerServiceInternal.class)).when( - () -> LocalServices.getService(PermissionManagerServiceInternal.class)); - doReturn(mock(ActivityManagerInternal.class)).when( - () -> LocalServices.getService(ActivityManagerInternal.class)); - doReturn(mock(UserManagerInternal.class)).when( - () -> LocalServices.getService(UserManagerInternal.class)); - doReturn(mock(ActivityTaskManagerInternal.class)).when( - () -> LocalServices.getService(ActivityTaskManagerInternal.class)); - doReturn(mock(LegacyPermissionManagerInternal.class)).when( - () -> LocalServices.getService(LegacyPermissionManagerInternal.class)); - doReturn(mock(RoleManager.class)).when(mContext).getSystemService(RoleManager.class); - doReturn(mAppOpsManager).when(mContext).getSystemService(Context.APP_OPS_SERVICE); - doReturn(mApplicationInfo).when(mVoiceInteractionManagerServiceImpl).getApplicationInfo(); - - mVoiceInteractionManagerService = new VoiceInteractionManagerService(mContext); - mVoiceInteractionManagerServiceStub = - mVoiceInteractionManagerService.new VoiceInteractionManagerServiceStub(); - mVoiceInteractionManagerServiceStub.mImpl = mVoiceInteractionManagerServiceImpl; - mPermissionEnforcer.grant(Manifest.permission.MANAGE_HOTWORD_DETECTION); - } - - @Test - public void setShouldReceiveSandboxedTrainingData_currentAndPreinstalledAssistant_setsOp() { - // Set application info so current app is the current and preinstalled assistant. - mApplicationInfo.uid = Process.myUid(); - mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; - - mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData( - /* allowed= */ true); - - verify(mAppOpsManager).setUidMode(mOpIdCaptor.capture(), mUidCaptor.capture(), - mOpModeCaptor.capture()); - assertThat(mOpIdCaptor.getValue()).isEqualTo( - AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA); - assertThat(mOpModeCaptor.getValue()).isEqualTo(AppOpsManager.MODE_ALLOWED); - assertThat(mUidCaptor.getValue()).isEqualTo(Process.myUid()); - } - - @Test - public void setShouldReceiveSandboxedTrainingData_missingPermission_doesNotSetOp() { - // Set application info so current app is the current and preinstalled assistant. - mApplicationInfo.uid = Process.myUid(); - mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; - - // Simulate missing MANAGE_HOTWORD_DETECTION permission. - mPermissionEnforcer.revoke(Manifest.permission.MANAGE_HOTWORD_DETECTION); - - assertThrows(SecurityException.class, - () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData( - /* allowed= */ true)); - - verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); - } - - @Test - public void setShouldReceiveSandboxedTrainingData_notPreinstalledAssistant_doesNotSetOp() { - // Set application info so current app is not preinstalled assistant. - mApplicationInfo.uid = Process.myUid(); - mApplicationInfo.flags = ApplicationInfo.FLAG_INSTALLED; // Does not contain FLAG_SYSTEM. - - assertThrows(SecurityException.class, - () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData( - /* allowed= */ true)); - - verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); - } - - @Test - public void setShouldReceiveSandboxedTrainingData_notCurrentAssistant_doesNotSetOp() { - // Set application info so current app is not current assistant. - mApplicationInfo.uid = Process.SHELL_UID; // Set current assistant uid to shell UID. - mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM; - - assertThrows(SecurityException.class, - () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData( - /* allowed= */ true)); - - verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt()); - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index 887e5ee0c58a..60dfe6f01817 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -811,7 +811,7 @@ public class ContentRecorderTests extends WindowTestsBase { @Test public void testDisplayContentUpdatesRecording_withSurface() { - defaultInit(); + createContentRecorder(createDefaultDisplayInfo()); // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to // mirror. setUpDefaultTaskDisplayAreaWindowToken(); @@ -820,6 +820,7 @@ public class ContentRecorderTests extends WindowTestsBase { // getDisplaySurfaceDefaultSize (done by surfaceControlMirrors in setUp). final DisplayContent virtualDisplay = mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId()); + virtualDisplay.setContentRecorder(mContentRecorder); mWm.mContentRecordingController.setContentRecordingSessionLocked(mDisplaySession, mWm); virtualDisplay.updateRecording(); @@ -844,6 +845,7 @@ public class ContentRecorderTests extends WindowTestsBase { // WHEN getting the DisplayContent for the new virtual display. final DisplayContent virtualDisplay = mRootWindowContainer.getDisplayContent(mDisplaySession.getVirtualDisplayId()); + virtualDisplay.setContentRecorder(mContentRecorder); // Return the default display as the value to mirror to ensure the VD with flag mirroring // creates a ContentRecordingSession automatically. doReturn(DEFAULT_DISPLAY).when(mWm.mDisplayManagerInternal).getDisplayIdToMirror(anyInt()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java index c84fe0871d3f..42004c365ba5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java @@ -24,6 +24,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.platform.test.annotations.Presubmit; @@ -31,6 +32,8 @@ import android.view.ContentRecordingSession; import androidx.test.filters.SmallTest; +import com.android.server.wm.ContentRecorder.MediaProjectionManagerWrapper; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -68,6 +71,11 @@ public class ContentRecordingControllerTests extends WindowTestsBase { mVirtualDisplayId = mVirtualDisplayContent.getDisplayId(); mWm.mRoot.onDisplayAdded(mVirtualDisplayId); spyOn(mVirtualDisplayContent); + final MediaProjectionManagerWrapper + mediaProjectionManagerWrapper = mock(MediaProjectionManagerWrapper.class); + final ContentRecorder contentRecorder = new ContentRecorder(mVirtualDisplayContent, + mediaProjectionManagerWrapper, /* correctForAnisotropicPixels= */ false); + mVirtualDisplayContent.setContentRecorder(contentRecorder); mDefaultSession.setVirtualDisplayId(mVirtualDisplayId); mWaitingDisplaySession.setVirtualDisplayId(mVirtualDisplayId); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java index 06d30fc98c6a..29f48b86a375 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java @@ -58,7 +58,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase { public void setUp() { super.setUp(); MockitoAnnotations.initMocks(this); - mCache = new TaskSnapshotCache(mWm, mLoader); + mCache = new TaskSnapshotCache(mLoader); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java index df5f3d149e88..7432537902a0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java @@ -61,7 +61,7 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas public void setUp() { super.setUp(); MockitoAnnotations.initMocks(this); - mCache = new TaskSnapshotCache(mWm, mLoader); + mCache = new TaskSnapshotCache(mLoader); } @Test diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index fc7c6a6d0258..55a89239b864 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -319,6 +319,16 @@ public class UsbPortManager implements IBinder.DeathRecipient { } /** + * Returns true if the provided port supports changing its mode. + */ + public boolean isModeChangeSupported(String portId) { + synchronized (mLock) { + final PortInfo portInfo = mPorts.get(portId); + return portInfo != null ? portInfo.mCanChangeMode : false; + } + } + + /** * Enables/disables contaminant detection. * * @param portId port identifier. diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index fb13b33a30ce..530a39e8b53e 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -781,6 +781,20 @@ public class UsbService extends IUsbManager.Stub { } } + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USB) + @Override + public boolean isModeChangeSupported(String portId) { + isModeChangeSupported_enforcePermission(); + Objects.requireNonNull(portId, "portId must not be null"); + + final long ident = Binder.clearCallingIdentity(); + try { + return mPortManager != null ? mPortManager.isModeChangeSupported(portId) : false; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + @Override public void setPortRoles(String portId, int powerRole, int dataRole) { Objects.requireNonNull(portId, "portId must not be null"); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java index 20a0850db6b8..368a96b372fe 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java @@ -73,6 +73,8 @@ import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteException; import android.os.SharedMemory; +import android.service.voice.AlwaysOnHotwordDetector; +import android.service.voice.HotwordAudioStream; import android.service.voice.HotwordDetectedResult; import android.service.voice.HotwordDetectionService; import android.service.voice.HotwordDetectionServiceFailure; @@ -81,6 +83,7 @@ import android.service.voice.HotwordRejectedResult; import android.service.voice.IDspHotwordDetectionCallback; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.VisualQueryDetectionServiceFailure; +import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback; import android.text.TextUtils; import android.util.Pair; import android.util.Slog; @@ -405,7 +408,83 @@ abstract class DetectorSession { audioStream, audioFormat, options, - callback); + callback, + /* shouldCloseAudioStreamWithDelayOnDetect= */ true); + } + + void startListeningFromWearableLocked( + ParcelFileDescriptor audioStream, + AudioFormat audioFormat, + PersistableBundle options, + WearableHotwordDetectionCallback wearableCallback) { + if (DEBUG) { + Slog.d(TAG, "startListeningFromWearableLocked"); + } + IMicrophoneHotwordDetectionVoiceInteractionCallback voiceInteractionCallback = + new IMicrophoneHotwordDetectionVoiceInteractionCallback() { + @Override + public void onDetected( + HotwordDetectedResult hotwordDetectedResult, + AudioFormat audioFormatFromCallback, + ParcelFileDescriptor audioStreamFromCallback) { + wearableCallback.onDetected(); + try { + // This uses the DSP hotword code path to send the result to + // AlwaysOnHotwordDetector. DSP trigger and wearable trigger operates + // independently. + mCallback.onKeyphraseDetectedFromExternalSource(hotwordDetectedResult); + } catch (RemoteException ex) { + Slog.w( + TAG, + "RemoteException when sending HotwordDetectedResult to" + + " VoiceInteractionService.", + ex); + wearableCallback.onError( + "RemoteException when sending HotwordDetectedResult to" + + " VoiceInteractionService."); + notifyOnDetectorRemoteException(); + } + + // Close the local copies of the file descriptors after sending them to + // another process. + for (HotwordAudioStream resultAudioStream : + hotwordDetectedResult.getAudioStreams()) { + try { + resultAudioStream.getAudioStreamParcelFileDescriptor().close(); + } catch (IOException ex) { + Slog.i( + TAG, + "Unable to close audio stream parcel file descriptor,", + ex); + } + } + } + + @Override + public void onHotwordDetectionServiceFailure( + HotwordDetectionServiceFailure hotwordDetectionServiceFailure) { + wearableCallback.onError( + "onHotwordDetectionServiceFailure: " + + hotwordDetectionServiceFailure); + } + + @Override + public void onRejected(HotwordRejectedResult hotwordRejectedResult) { + wearableCallback.onRejected(); + } + + @Override + public IBinder asBinder() { + // This callback will only be used locally within the same process. + return null; + } + }; + handleExternalSourceHotwordDetectionLocked( + audioStream, + audioFormat, + options, + voiceInteractionCallback, + /* shouldCloseAudioStreamWithDelayOnDetect= */ false); } @SuppressWarnings("GuardedBy") @@ -413,7 +492,8 @@ abstract class DetectorSession { ParcelFileDescriptor audioStream, AudioFormat audioFormat, @Nullable PersistableBundle options, - IMicrophoneHotwordDetectionVoiceInteractionCallback callback) { + IMicrophoneHotwordDetectionVoiceInteractionCallback callback, + boolean shouldCloseAudioStreamWithDelayOnDetect) { if (DEBUG) { Slog.d(TAG, "#handleExternalSourceHotwordDetectionLocked"); } @@ -482,12 +562,22 @@ abstract class DetectorSession { // TODO: what if we cancelled and started a new one? mRemoteDetectionService.run( service -> { + PersistableBundle optionsToSend = options; + if (android.app.wearable.Flags.enableHotwordWearableSensingApi()) { + if (optionsToSend == null) { + optionsToSend = new PersistableBundle(); + } + optionsToSend.putBoolean( + HotwordDetectionService + .KEY_SYSTEM_WILL_CLOSE_AUDIO_STREAM_AFTER_CALLBACK, + shouldCloseAudioStreamWithDelayOnDetect); + } service.detectFromMicrophoneSource( serviceAudioSource, // TODO: consider making a proxy callback + copy of audio format AUDIO_SOURCE_EXTERNAL, audioFormat, - options, + optionsToSend, new IDspHotwordDetectionCallback.Stub() { @Override public void onRejected(HotwordRejectedResult result) @@ -530,18 +620,23 @@ abstract class DetectorSession { getDetectorType(), METRICS_EXTERNAL_SOURCE_DETECTED, mVoiceInteractionServiceUid); - mScheduledExecutorService.schedule( - () -> { - bestEffortClose(serviceAudioSink, audioSource); - }, - EXTERNAL_HOTWORD_CLEANUP_MILLIS, - TimeUnit.MILLISECONDS); - + if (shouldCloseAudioStreamWithDelayOnDetect) { + mScheduledExecutorService.schedule( + () -> { + bestEffortClose( + serviceAudioSink, audioSource); + }, + EXTERNAL_HOTWORD_CLEANUP_MILLIS, + TimeUnit.MILLISECONDS); + } try { enforcePermissionsForDataDelivery(); } catch (SecurityException e) { - Slog.w(TAG, "Ignoring #onDetected due to a " - + "SecurityException", e); + Slog.w( + TAG, + "Ignoring #onDetected due to a " + + "SecurityException", + e); HotwordMetricsLogger.writeDetectorEvent( getDetectorType(), EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION, @@ -560,11 +655,16 @@ abstract class DetectorSession { } HotwordDetectedResult newResult; try { - newResult = mHotwordAudioStreamCopier - .startCopyingAudioStreams(triggerResult); + newResult = + mHotwordAudioStreamCopier + .startCopyingAudioStreams( + triggerResult); } catch (IOException e) { - Slog.w(TAG, "Ignoring #onDetected due to a " - + "IOException", e); + Slog.w( + TAG, + "Ignoring #onDetected due to a " + + "IOException", + e); // TODO: Write event try { callback.onHotwordDetectionServiceFailure( @@ -578,7 +678,12 @@ abstract class DetectorSession { return; } try { - callback.onDetected(newResult, /* audioFormat= */ null, + // The ParcelFileDescriptors in newResult might be + // closed after this call. Parcelling newResult can + // throw an exception + callback.onDetected( + newResult, + /* audioFormat= */ null, /* audioStream= */ null); } catch (RemoteException e) { notifyOnDetectorRemoteException(); @@ -588,8 +693,7 @@ abstract class DetectorSession { + HotwordDetectedResult.getUsageSize(newResult) + " bits from hotword trusted process"); if (mDebugHotwordLogging) { - Slog.i(TAG, - "Egressed detected result: " + newResult); + Slog.i(TAG, "Egressed detected result: " + newResult); } } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index cd390a17fc4d..f1f5458f161c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -63,6 +63,7 @@ import android.service.voice.SoundTriggerFailure; import android.service.voice.VisualQueryDetectionService; import android.service.voice.VisualQueryDetectionServiceFailure; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; +import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback; import android.speech.IRecognitionServiceManager; import android.util.Slog; import android.util.SparseArray; @@ -451,6 +452,25 @@ final class HotwordDetectionConnection { session.startListeningFromExternalSourceLocked(audioStream, audioFormat, options, callback); } + public void startListeningFromWearableLocked( + ParcelFileDescriptor audioStream, + AudioFormat audioFormat, + PersistableBundle options, + WearableHotwordDetectionCallback callback) { + if (DEBUG) { + Slog.d(TAG, "startListeningFromWearableLocked"); + } + DetectorSession trustedSession = getDspTrustedHotwordDetectorSessionLocked(); + if (trustedSession == null) { + callback.onError( + "Unable to start listening from wearable because the trusted hotword detection" + + " session is not available."); + return; + } + trustedSession.startListeningFromWearableLocked( + audioStream, audioFormat, options, callback); + } + /** * This method is only used by SoftwareHotwordDetector. */ diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 952a24f20fc0..ecb0f9689ae7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -16,16 +16,15 @@ package com.android.server.voiceinteraction; + import android.Manifest; import android.annotation.CallbackExecutor; -import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; -import android.app.AppOpsManager; import android.app.role.OnRoleHoldersChangedListener; import android.app.role.RoleManager; import android.content.ComponentName; @@ -76,6 +75,7 @@ import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; import android.service.voice.IVoiceInteractionSession; import android.service.voice.VoiceInteractionManagerInternal; +import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.service.voice.VoiceInteractionSession; @@ -321,6 +321,46 @@ public class VoiceInteractionManagerService extends SystemService { mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT, UserHandle.of(userId)); } + + @Override + public void startListeningFromWearable( + ParcelFileDescriptor audioStreamFromWearable, + AudioFormat audioFormatFromWearable, + PersistableBundle options, + ComponentName targetVisComponentName, + int userId, + WearableHotwordDetectionCallback callback) { + Slog.d(TAG, "#startListeningFromWearable"); + VoiceInteractionManagerServiceImpl impl = mServiceStub.mImpl; + if (impl == null) { + callback.onError( + "Unable to start listening from wearable because the service impl is" + + " null."); + return; + } + if (targetVisComponentName != null && !targetVisComponentName.equals(impl.mComponent)) { + callback.onError( + TextUtils.formatSimple( + "Unable to start listening from wearable because the target" + + " VoiceInteractionService %s is different from the current" + + " VoiceInteractionService %s", + targetVisComponentName, impl.mComponent)); + return; + } + if (userId != impl.mUser) { + callback.onError( + TextUtils.formatSimple( + "Unable to start listening from wearable because the target userId" + + " %s is different from the current" + + " VoiceInteractionManagerServiceImpl's userId %s", + userId, impl.mUser)); + return; + } + synchronized (mServiceStub) { + impl.startListeningFromWearableLocked( + audioStreamFromWearable, audioFormatFromWearable, options, callback); + } + } } // implementation entry point and binder service @@ -1544,34 +1584,6 @@ public class VoiceInteractionManagerService extends SystemService { } } - @Override - @EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) - public void setShouldReceiveSandboxedTrainingData(boolean allowed) { - super.setShouldReceiveSandboxedTrainingData_enforcePermission(); - - synchronized (this) { - if (mImpl == null) { - throw new IllegalStateException( - "setShouldReceiveSandboxedTrainingData without running voice " - + "interaction service"); - } - - enforceIsCallerPreinstalledAssistant(); - - int callingUid = Binder.getCallingUid(); - final long caller = Binder.clearCallingIdentity(); - try { - AppOpsManager appOpsManager = (AppOpsManager) - mContext.getSystemService(Context.APP_OPS_SERVICE); - appOpsManager.setUidMode( - AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, - callingUid, allowed ? AppOpsManager.MODE_ALLOWED : - AppOpsManager.MODE_ERRORED); - } finally { - Binder.restoreCallingIdentity(caller); - } - } - } //----------------- Model management APIs --------------------------------// @@ -1736,7 +1748,10 @@ public class VoiceInteractionManagerService extends SystemService { if (keyphrase.equals(phrase.getText())) { ArraySet<Locale> locales = new ArraySet<>(); locales.add(phrase.getLocale()); - return new KeyphraseMetadata(phrase.getId(), phrase.getText(), locales, + return new KeyphraseMetadata( + phrase.getId(), + phrase.getText(), + locales, phrase.getRecognitionModes()); } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 7538142d094f..84b36d5948eb 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -65,6 +65,7 @@ import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; +import android.service.voice.VoiceInteractionManagerInternal.WearableHotwordDetectionCallback; import android.service.voice.VoiceInteractionService; import android.service.voice.VoiceInteractionServiceInfo; import android.system.OsConstants; @@ -857,6 +858,24 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne options, token, callback); } + public void startListeningFromWearableLocked( + ParcelFileDescriptor audioStream, + AudioFormat audioFormat, + PersistableBundle options, + WearableHotwordDetectionCallback callback) { + if (DEBUG) { + Slog.d(TAG, "startListeningFromWearable"); + } + if (mHotwordDetectionConnection == null) { + callback.onError( + "Unable to start listening from wearable because the hotword detection" + + " connection is null."); + return; + } + mHotwordDetectionConnection.startListeningFromWearableLocked( + audioStream, audioFormat, options, callback); + } + public void stopListeningFromMicLocked() { if (DEBUG) { Slog.d(TAG, "stopListeningFromMicLocked"); diff --git a/tests/Input/AndroidTest.xml b/tests/Input/AndroidTest.xml index c62db1ea5ca9..13b5f0d99ba8 100644 --- a/tests/Input/AndroidTest.xml +++ b/tests/Input/AndroidTest.xml @@ -4,6 +4,7 @@ --> <configuration description="Runs Input Tests"> <option name="test-tag" value="InputTests" /> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> <!-- keeps the screen on during tests --> <option name="screen-always-on" value="on" /> diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java index 14230fe4c323..0fb4f90f354f 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java @@ -42,6 +42,7 @@ import android.view.SurfaceControlViewHost; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.TextView; +import android.window.InputTransferToken; public class EmbeddedWindowService extends Service { private static final String TAG = "EmbeddedWindowService"; @@ -118,7 +119,7 @@ public class EmbeddedWindowService extends Service { @Override public void attachEmbeddedSurfaceControl(SurfaceControl parentSc, int displayId, - IBinder hostToken) { + InputTransferToken inputTransferToken) { mHandler.post(() -> { Paint paint = new Paint(); paint.setTextSize(40); @@ -134,7 +135,7 @@ public class EmbeddedWindowService extends Service { c.drawText("Remote", 250, 250, paint); surface.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); - wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken, + wm.registerBatchedSurfaceControlInputReceiver(displayId, inputTransferToken, mSurfaceControl, Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-remote " + event); diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl index 6b65b40ef8c6..e81f5f81481a 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/IAttachEmbeddedWindow.aidl @@ -20,10 +20,12 @@ import android.os.IBinder; import com.android.test.viewembed.IAttachEmbeddedWindowCallback; import android.view.WindowManager.LayoutParams; import android.view.SurfaceControl; +import android.window.InputTransferToken; interface IAttachEmbeddedWindow { void attachEmbedded(IBinder hostToken, int width, int height, in IAttachEmbeddedWindowCallback callback); void relayout(in LayoutParams lp); - oneway void attachEmbeddedSurfaceControl(in SurfaceControl parentSurfaceControl, int displayId, IBinder hostToken); + oneway void attachEmbeddedSurfaceControl(in SurfaceControl parentSurfaceControl, int displayId, + in InputTransferToken inputTransferToken); oneway void tearDownEmbeddedSurfaceControl(); }
\ No newline at end of file diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java index 7330ec14011b..e700bc2f3d21 100644 --- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java +++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java @@ -139,7 +139,7 @@ public class SurfaceInputTestActivity extends Activity { surface.unlockCanvasAndPost(c); WindowManager wm = getSystemService(WindowManager.class); wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), - attachedSurfaceControl.getHostToken(), mLocalSurfaceControl, + attachedSurfaceControl.getInputTransferToken(), mLocalSurfaceControl, Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-sc " + event); return false; @@ -160,7 +160,8 @@ public class SurfaceInputTestActivity extends Activity { WindowManager wm = getSystemService(WindowManager.class); wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(), - mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(), + mLocalSurfaceView.getRootSurfaceControl().getInputTransferToken(), + mLocalSurfaceView.getSurfaceControl(), Choreographer.getInstance(), event -> { Log.d(TAG, "onInputEvent-local " + event); return false; @@ -210,7 +211,8 @@ public class SurfaceInputTestActivity extends Activity { } try { mIAttachEmbeddedWindow.attachEmbeddedSurfaceControl(mParentSurfaceControl, - getDisplayId(), mRemoteSurfaceView.getHostToken()); + getDisplayId(), + mRemoteSurfaceView.getRootSurfaceControl().getInputTransferToken()); } catch (RemoteException e) { Log.e(TAG, "Failed to load embedded SurfaceControl", e); } diff --git a/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java index dabfcae8e0fd..64d761d4aad6 100644 --- a/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java +++ b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java @@ -58,7 +58,7 @@ public class UsbPortStatusTest { isSinkDeviceRoleSupported, isSinkHostRoleSupported, isSourceDeviceRoleSupported, - isSinkHostRoleSupported); + isSourceHostRoleSupported); UsbPortStatus usbPortStatus = new UsbPortStatus( MODE_NONE, POWER_ROLE_NONE, diff --git a/tests/UsbManagerTests/src/android/hardware/usb/UsbPortTest.java b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortTest.java new file mode 100644 index 000000000000..afd141935d08 --- /dev/null +++ b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortTest.java @@ -0,0 +1,76 @@ +/* + * 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.hardware.usb; + +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE; +import static android.hardware.usb.UsbPortStatus.MODE_NONE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.hardware.usb.flags.Flags; +import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import androidx.test.InstrumentationRegistry; + +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link android.hardware.usb.UsbPortStatus} */ +@RunWith(TestParameterInjector.class) +public class UsbPortTest { + + private IUsbManager mMockUsbService; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + private UsbManager mUsbManager; + + @Before + public void setUp() throws Exception { + mMockUsbService = mock(IUsbManager.class); + mUsbManager = new UsbManager(InstrumentationRegistry.getContext(), mMockUsbService); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_IS_MODE_CHANGE_SUPPORTED_API) + public void testIsModeSupported(@TestParameter boolean isModeChangeSupported) + throws RemoteException { + String testPortId = "port-1"; + when(mMockUsbService.isModeChangeSupported(testPortId)).thenReturn(isModeChangeSupported); + UsbPort usbPort = new UsbPort( + mUsbManager, + testPortId, + MODE_NONE, + CONTAMINANT_PROTECTION_NONE, + false /* supportsEnableContaminantPresenceProtection= */ , + false /* supportsEnableContaminantPresenceDetection= */); + + assertThat(usbPort.isModeChangeSupported()).isEqualTo(isModeChangeSupported); + } +} diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index c6dd29ce7cc6..30333da5e86c 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -54,6 +54,7 @@ java_library { // This library is _not_ specific to Android APIs. java_library_host { name: "hoststubgen-helper-runtime", + defaults: ["ravenwood-internal-only-visibility-java"], srcs: [ "helper-runtime-src/**/*.java", ], @@ -64,11 +65,11 @@ java_library_host { "guava", ], jarjar_rules: "jarjar-rules.txt", - visibility: ["//visibility:public"], } java_library { name: "hoststubgen-helper-runtime.ravenwood", + defaults: ["ravenwood-internal-only-visibility-java"], srcs: [ "helper-runtime-src/**/*.java", ], @@ -79,7 +80,6 @@ java_library { "guava", ], jarjar_rules: "jarjar-rules.txt", - visibility: ["//visibility:public"], } // Host-side stub generator tool. @@ -152,34 +152,3 @@ genrule_defaults { "hoststubgen_dump.txt", ], } - -java_library_host { - name: "hoststubgen-helper-libcore-runtime", - srcs: [ - "helper-framework-runtime-src/libcore-fake/**/*.java", - ], - visibility: ["//visibility:private"], -} - -java_host_for_device { - name: "hoststubgen-helper-libcore-runtime.ravenwood", - libs: [ - "hoststubgen-helper-libcore-runtime", - ], - visibility: ["//visibility:private"], -} - -java_library { - name: "hoststubgen-helper-framework-runtime.ravenwood", - defaults: ["ravenwood-internal-only-visibility-java"], - srcs: [ - "helper-framework-runtime-src/framework/**/*.java", - ], - libs: [ - "hoststubgen-helper-runtime.ravenwood", - "framework-minus-apex.ravenwood", - ], - static_libs: [ - "hoststubgen-helper-libcore-runtime.ravenwood", - ], -} diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt index 3c55237ce443..ce856cd49614 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt @@ -25,6 +25,7 @@ import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.OutputStream +import java.time.LocalDateTime import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.jar.JarOutputStream @@ -42,6 +43,13 @@ object ProtoLogTool { return source.contains(protoLogSimpleClassName) } + private fun zipEntry(path: String): ZipEntry { + val entry = ZipEntry(path) + // Use a constant time to improve the cachability of build actions. + entry.timeLocal = LocalDateTime.of(2008, 1, 1, 0, 0, 0) + return entry + } + private fun processClasses(command: CommandOptions) { val groups = injector.readLogGroups( command.protoLogGroupsJarArg, @@ -77,7 +85,7 @@ object ProtoLogTool { } }.map { future -> val (path, outSrc) = future.get() - outJar.putNextEntry(ZipEntry(path)) + outJar.putNextEntry(zipEntry(path)) outJar.write(outSrc.toByteArray()) outJar.closeEntry() } @@ -90,7 +98,7 @@ object ProtoLogTool { val cachePackage = cacheSplit.dropLast(1).joinToString(".") val cachePath = "gen/${cacheSplit.joinToString("/")}.java" - outJar.putNextEntry(ZipEntry(cachePath)) + outJar.putNextEntry(zipEntry(cachePath)) outJar.write(generateLogGroupCache(cachePackage, cacheName, groups, command.protoLogImplClassNameArg, command.protoLogGroupsClassNameArg).toByteArray()) |