Merge "isSubscriptionAssociatedWithUser API" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 7e6c30f..2846221 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -75,6 +75,7 @@
":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 @@
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 2babf6a..6337022 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -93,16 +93,33 @@
],
}
+// 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 cf9c664..ec8699a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5329,6 +5329,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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";
@@ -34028,6 +34076,9 @@
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 {
@@ -39648,7 +39699,7 @@
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();
@@ -39656,7 +39707,7 @@
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();
@@ -39688,7 +39739,7 @@
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);
@@ -39793,14 +39844,14 @@
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();
@@ -39822,7 +39873,7 @@
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);
@@ -50510,7 +50561,7 @@
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);
@@ -52220,6 +52271,7 @@
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);
@@ -52421,7 +52473,7 @@
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);
@@ -52531,8 +52583,8 @@
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();
@@ -52917,7 +52969,7 @@
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);
@@ -53796,11 +53848,11 @@
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();
@@ -53825,7 +53877,7 @@
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);
@@ -54366,9 +54418,9 @@
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>);
@@ -55199,6 +55251,7 @@
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 {
@@ -61349,6 +61402,12 @@
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);
@@ -61396,11 +61455,11 @@
@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 b36b963f..9b8ab9b 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -245,6 +245,14 @@
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 @@
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 @@
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 88f9aff..20a621ba 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -100,6 +100,7 @@
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 @@
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";
@@ -1307,6 +1309,7 @@
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);
@@ -1329,6 +1332,7 @@
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);
@@ -3191,6 +3195,8 @@
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
@@ -3677,6 +3683,7 @@
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";
@@ -4670,11 +4677,15 @@
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 {
@@ -4684,6 +4695,7 @@
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();
}
@@ -10430,9 +10442,11 @@
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
@@ -11082,9 +11096,6 @@
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";
}
@@ -13188,6 +13199,7 @@
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
}
@@ -13294,6 +13306,7 @@
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";
}
@@ -13578,7 +13591,11 @@
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 cd84c84..f6366a2 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1519,6 +1519,7 @@
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 {
@@ -2277,9 +2278,7 @@
}
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 {
@@ -3172,6 +3171,7 @@
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 ab9a4ec..23fe731 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3927,6 +3927,7 @@
if (keyCode == KeyEvent.KEYCODE_ESCAPE && mWindow.shouldCloseOnTouchOutside()) {
event.startTracking();
+ finish();
return true;
}
@@ -4027,10 +4028,7 @@
}
if (keyCode == KeyEvent.KEYCODE_ESCAPE
- && mWindow.shouldCloseOnTouchOutside()
- && event.isTracking()
- && !event.isCanceled()) {
- finish();
+ && event.isTracking()) {
return true;
}
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index c6712c0..3715c6e 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -22,6 +22,7 @@
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 @@
private @StartType int mStartType;
/**
- * @see #getStartIntent
+ * @see #getIntent
*/
private Intent mStartIntent;
@@ -259,6 +260,11 @@
private @LaunchMode int mLaunchMode;
/**
+ * @see #wasForceStopped()
+ */
+ private boolean mWasForceStopped;
+
+ /**
* @hide *
*/
@IntDef(
@@ -427,6 +433,15 @@
}
/**
+ * @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 @@
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 @@
dest.writeInt(mStartType);
dest.writeParcelable(mStartIntent, flags);
dest.writeInt(mLaunchMode);
+ dest.writeBoolean(mWasForceStopped);
}
/** @hide */
@@ -622,6 +652,7 @@
mStartType = other.mStartType;
mStartIntent = other.mStartIntent;
mLaunchMode = other.mLaunchMode;
+ mWasForceStopped = other.mWasForceStopped;
}
private ApplicationStartInfo(@NonNull Parcel in) {
@@ -643,6 +674,7 @@
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 @@
intentOut.close();
}
proto.write(ApplicationStartInfoProto.LAUNCH_MODE, mLaunchMode);
+ proto.write(ApplicationStartInfoProto.WAS_FORCE_STOPPED, mWasForceStopped);
proto.end(token);
}
@@ -799,6 +832,10 @@
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 @@
.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 @@
&& 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 d0d76a4..0e20138 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -672,7 +672,16 @@
*/
@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 @@
}
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 d7aafa0..a884ab0 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 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 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 9d50810..34fb754 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_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.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 @@
}
/**
- * 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 @@
* 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 @@
* 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 @@
* 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 @@
}
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 304359b..07ee8de 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -333,4 +333,9 @@
*/
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 f72fdc0..f2466ac 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -392,7 +392,7 @@
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 @@
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 30cd1b7..726ddad 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -92,6 +92,13 @@
}
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 7a4a3f9..9fa7362 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 @@
*
* @hide
*/
- @FlaggedApi("autofill_credman_dev_integration")
+ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
public GetCredentialRequest getCredentialManagerRequest() {
return mGetCredentialRequest;
@@ -1291,7 +1293,7 @@
* @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 3cbc8a2..f678022 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 @@
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 3b281e9..637f677 100644
--- a/core/java/android/app/wearable/WearableSensingManager.java
+++ b/core/java/android/app/wearable/WearableSensingManager.java
@@ -28,6 +28,7 @@
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 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 @@
}
}
+ /**
+ * 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 e8031a3..0bcbb8e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -19,6 +19,7 @@
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 @@
"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 529363f..8220313 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -19,7 +19,9 @@
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 @@
}
}
+
+ /**
+ * 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 @@
}
}
+ /**
+ * 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 7c264f6..9c859c4 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 @@
/**
* 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 @@
* </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 @@
/**
* 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 214c3e4..8426f54 100644
--- a/core/java/android/content/pm/LauncherUserInfo.java
+++ b/core/java/android/content/pm/LauncherUserInfo.java
@@ -27,8 +27,6 @@
/**
* 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 @@
/**
* 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 @@
* {@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 c7d93bf..6696ba0 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -139,6 +139,20 @@
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/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 870546a..ba356bb 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -40,7 +40,7 @@
*/
@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 0000000..838e41e
--- /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 2ac21d2..19ae302 100644
--- a/core/java/android/hardware/ISensorPrivacyListener.aidl
+++ b/core/java/android/hardware/ISensorPrivacyListener.aidl
@@ -25,5 +25,6 @@
// 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 9cf329c..851ce2a 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 @@
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 @@
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 18c95bfb..6294a8d 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.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 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 @@
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 boolean isEnabled() {
return mEnabled;
}
+
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ public @StateTypes.StateType int getState() {
+ return mState;
+ }
+
}
}
@@ -319,6 +369,9 @@
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 @@
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)));
+ 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, state)));
+ }
+ }
+ }
+
};
/** Whether the singleton ISensorPrivacyListener has been registered */
@@ -649,6 +723,73 @@
}
/**
+ * 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 @@
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
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 451d6fb..2add77e 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -21,6 +21,7 @@
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 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 3b10e0d..f6b22ed 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -26,6 +26,7 @@
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.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 @@
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 7cf10d8..e24c98e 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -21,6 +21,7 @@
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 @@
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 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 ded96a2..66efccd1 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -21,6 +21,7 @@
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 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 7cf5a7f..a01c23d 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -22,6 +22,7 @@
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 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 0000000..32039c6
--- /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 0000000..5c99909
--- /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 0000000..15e8982
--- /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/os/BugreportParams.java b/core/java/android/os/BugreportParams.java
index f2ef185..f7b4173 100644
--- a/core/java/android/os/BugreportParams.java
+++ b/core/java/android/os/BugreportParams.java
@@ -21,7 +21,6 @@
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 @@
/**
* 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 @@
* 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 fbec518..3950c25 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -42,7 +42,7 @@
*/
@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 8e860c3..ccfb632 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -233,7 +233,8 @@
* {@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 6532d5c..17dfdda 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -75,7 +75,8 @@
* 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/SystemProperties.java b/core/java/android/os/SystemProperties.java
index a818919..0a38691 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -56,7 +56,8 @@
*/
@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 89576ed..2b30a2b 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -163,19 +163,16 @@
* 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 @@
* 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 @@
/**
* 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 @@
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/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 9d7fb70..e8c8b5a 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -96,9 +96,18 @@
}
flag {
+ name: "sensitive_notification_app_protection"
+ namespace: "permissions"
+ description: "This flag controls the sensitive notification app protections while screen sharing"
+ bug: "312784351"
+ # Referenced in WM where WM starts before DeviceConfig
+ is_fixed_read_only: true
+}
+
+flag {
name: "device_aware_permissions_enabled"
is_fixed_read_only: true
namespace: "permissions"
description: "When the flag is off no permissions can be device aware"
bug: "274852670"
-}
\ No newline at end of file
+}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 43163b3..7631454 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -15,10 +15,11 @@
}
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 786d768..aa47d3a 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -673,6 +673,10 @@
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 94d8516..a08264e 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.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 @@
@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 @@
@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 @@
mAudioStream = audioStream;
mKephraseExtras = keyphraseExtras;
mHalEventReceivedMillis = halEventReceivedMillis;
+ mIsRecognitionStopped = isRecognitionStopped;
}
/**
@@ -592,6 +598,12 @@
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 @@
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 @@
}
/**
+ * 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 @@
/**
* 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 @@
}
@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 ccf8b67..60e9de7 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -19,6 +19,7 @@
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 @@
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 @@
* 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 f1bc792..a835b0f 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -223,6 +223,13 @@
}
@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 23847fe..1eb4d67 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -414,6 +414,13 @@
}
@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 270f848..7d27733 100644
--- a/core/java/android/service/voice/VoiceInteractionManagerInternal.java
+++ b/core/java/android/service/voice/VoiceInteractionManagerInternal.java
@@ -19,14 +19,17 @@
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 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 @@
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/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index f67dcff..22d8fda 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -33,6 +33,10 @@
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 bb6e030..808c3ae 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -37,6 +37,7 @@
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 @@
"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 @@
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 @@
}
/**
+ * 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 @@
statusCallback.sendResult(bundle);
};
}
+
+
}
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index d2c5975..0a73fd1 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -50,7 +50,7 @@
*/
@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 31576c5..b33214d 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -73,7 +73,7 @@
*/
@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 ffe0c71..9413f5c 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -19,10 +19,11 @@
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 @@
}
/**
- * 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 58765b4..1dd9cbb 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.InputTransferToken;
import android.window.WindowTokenClient;
+import com.android.window.flags.Flags;
+
import dalvik.system.CloseGuard;
import java.util.Objects;
@@ -347,6 +350,25 @@
@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 254c4ae..25ade62 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -18,6 +18,7 @@
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 @@
*
* @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 @@
* @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 @@
*
* @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 @@
* @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 28a7334..657c8e6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -103,7 +103,6 @@
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.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 @@
}
/**
- * @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 d86cc4a..131fca7 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 @@
* {@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 @@
* {@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 @@
* @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 @@
* 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 58fb273..0302a0d 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -123,6 +123,7 @@
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 @@
* 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}
+ * { #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 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
+ * @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 @@
/**
* 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}
+ * 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 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
- **/
+ * @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 @@
* 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 @@
* 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 2fb5213..1e3d062 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -535,19 +535,19 @@
@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 fa0052c..749f977 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 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 @@
* @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 a11ac7c..5b99c71f 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 {
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/window/InputTransferToken.java b/core/java/android/window/InputTransferToken.java
index 0601b2a..bed0e0e 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.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 90f8834..a30c8fa 100644
--- a/core/java/android/window/TrustedPresentationThresholds.java
+++ b/core/java/android/window/TrustedPresentationThresholds.java
@@ -19,15 +19,15 @@
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 @@
@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)
- @SuppressLint("InternalField") // simple data class
- public final int stabilityRequirementMs;
+ 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)
+ 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 @@
/**
* 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 @@
@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 @@
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 069affb..3ffa274 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -20,14 +20,6 @@
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 ba87caa..5cb5963 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -40,6 +40,13 @@
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/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index ed943cb..eef6ce7 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -57,7 +57,7 @@
*/
@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 @@
*/
@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 064609f..e5662c7 100644
--- a/core/java/com/android/internal/os/LongMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter.java
@@ -57,7 +57,7 @@
*/
@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 8c33041..d9ed911 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -39,4 +39,5 @@
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 9359528..e368c6a 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,6 +91,9 @@
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 @@
// 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 e4e8f7e..e7df19c 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 647bccf..734ff8d 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 6be553b..beffb9a 100644
--- a/core/tests/BroadcastRadioTests/Android.bp
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -18,6 +18,7 @@
// 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 661b210..402d08e 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.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 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 aa89e04..cc342cf 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.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 @@
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/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 4982f37..244fe30 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -618,7 +618,7 @@
* @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 @@
* @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 @@
* <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 7b6b2d1..2495d1a 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -401,7 +401,7 @@
* @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 @@
* @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 @@
* <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 83ddfc5..e6c652c 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -974,7 +974,7 @@
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 2d8c5a3..e6a63b9 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -259,7 +259,7 @@
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/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 4ee25c4..ac94a6f 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 @@
*/
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 @@
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 @@
}
}
+ /**
+ * 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 @@
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 @@
mLinearBlock = block;
mOffset = offset;
mSize = size;
- mCryptoInfo = null;
+ mCryptoInfos.clear();
return this;
}
@@ -3498,7 +3586,44 @@
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 @@
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 @@
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 @@
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 @@
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 86f89ab..3174c37 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -20,6 +20,7 @@
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 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 @@
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/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index c4b38c7..b165809 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/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index fa9afa8..a8e9423 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -232,52 +232,8 @@
+ " 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 @@
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 @@
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 @@
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 @@
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 @@
* children changed.
*/
public void notifyChildrenChanged(@NonNull String parentId) {
- notifyChildrenChangedInternal(parentId, null);
+ notifyChildrenChanged(parentId, Bundle.EMPTY);
}
/**
@@ -617,30 +523,10 @@
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 @@
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 @@
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 @@
iter.remove();
}
}
- if (callbackList.size() == 0) {
+ if (callbackList.isEmpty()) {
connection.subscriptions.remove(id);
}
}
@@ -709,44 +682,53 @@
/**
* 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 @@
}
}
- 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 8cdd59e..8396005 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -458,6 +458,24 @@
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 @@
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 @@
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 @@
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 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 @@
"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 @@
"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 @@
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 @@
{ "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 @@
{ "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 02708ef..abb23f5 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -114,6 +114,14 @@
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 @@
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 64e8efe..6efb028 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -314,6 +314,23 @@
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 9605108..3302265 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -90,6 +90,7 @@
AInputEvent_getSource;
AInputEvent_getType;
AInputEvent_release; # introduced=31
+ AInputEvent_toJava; # introduced=35
AInputQueue_attachLooper;
AInputQueue_detachLooper;
AInputQueue_finishEvent;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt
new file mode 100644
index 0000000..cda6b8b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.media.session
+
+import android.media.session.MediaController
+import android.media.session.MediaSessionManager
+import android.os.UserHandle
+import androidx.concurrent.futures.DirectExecutor
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [Flow] for [MediaSessionManager.OnActiveSessionsChangedListener]. */
+val MediaSessionManager.activeMediaChanges: Flow<Collection<MediaController>?>
+ get() =
+ callbackFlow {
+ val listener =
+ MediaSessionManager.OnActiveSessionsChangedListener { launch { send(it) } }
+ addOnActiveSessionsChangedListener(
+ null,
+ UserHandle.of(UserHandle.myUserId()),
+ DirectExecutor.INSTANCE,
+ listener,
+ )
+ awaitClose { removeOnActiveSessionsChangedListener(listener) }
+ }
+ .buffer(capacity = Channel.CONFLATED)
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt
new file mode 100644
index 0000000..1f037c0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.os.Bundle
+import android.os.Handler
+import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.launch
+
+/** [MediaController.Callback] flow representation. */
+fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> {
+ return callbackFlow {
+ val callback = MediaControllerCallbackProducer(this)
+ registerCallback(callback, handler)
+ awaitClose { unregisterCallback(callback) }
+ }
+}
+
+/** Models particular change event received by [MediaController.Callback]. */
+sealed interface MediaControllerChange {
+
+ data object SessionDestroyed : MediaControllerChange
+
+ data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChange
+
+ data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChange
+
+ data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange
+
+ data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) :
+ MediaControllerChange
+
+ data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange
+
+ data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange
+
+ data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange
+}
+
+private class MediaControllerCallbackProducer(
+ private val producingScope: ProducerScope<MediaControllerChange>
+) : MediaController.Callback() {
+
+ override fun onSessionDestroyed() {
+ send(MediaControllerChange.SessionDestroyed)
+ }
+
+ override fun onSessionEvent(event: String, extras: Bundle?) {
+ send(MediaControllerChange.SessionEvent(event, extras))
+ }
+
+ override fun onPlaybackStateChanged(state: PlaybackState?) {
+ send(MediaControllerChange.PlaybackStateChanged(state))
+ }
+
+ override fun onMetadataChanged(metadata: MediaMetadata?) {
+ send(MediaControllerChange.MetadataChanged(metadata))
+ }
+
+ override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) {
+ send(MediaControllerChange.QueueChanged(queue))
+ }
+
+ override fun onQueueTitleChanged(title: CharSequence?) {
+ send(MediaControllerChange.QueueTitleChanged(title))
+ }
+
+ override fun onExtrasChanged(extras: Bundle?) {
+ send(MediaControllerChange.ExtrasChanged(extras))
+ }
+
+ override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) {
+ send(MediaControllerChange.AudioInfoChanged(info))
+ }
+
+ private fun send(change: MediaControllerChange) {
+ producingScope.launch { producingScope.send(change) }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
index ab8c6b8..6925c71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt
@@ -16,21 +16,23 @@
package com.android.settingslib.volume.data.repository
+import android.content.Intent
import android.media.AudioManager
import android.media.session.MediaController
import android.media.session.MediaSessionManager
import android.media.session.PlaybackState
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.bluetooth.headsetAudioModeChanges
+import com.android.settingslib.media.session.activeMediaChanges
import com.android.settingslib.volume.shared.AudioManagerIntentsReceiver
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
@@ -38,7 +40,7 @@
interface MediaControllerRepository {
/** Current [MediaController]. Null is emitted when there is no active [MediaController]. */
- val activeMediaController: StateFlow<MediaController?>
+ val activeLocalMediaController: StateFlow<MediaController?>
}
class MediaControllerRepositoryImpl(
@@ -53,26 +55,28 @@
audioManagerIntentsReceiver.intents.filter {
AudioManager.STREAM_DEVICES_CHANGED_ACTION == it.action
}
- override val activeMediaController: StateFlow<MediaController?> =
- buildList {
- localBluetoothManager?.headsetAudioModeChanges?.let { add(it) }
- add(devicesChanges)
+
+ override val activeLocalMediaController: StateFlow<MediaController?> =
+ combine(
+ mediaSessionManager.activeMediaChanges.onStart {
+ emit(mediaSessionManager.getActiveSessions(null))
+ },
+ localBluetoothManager?.headsetAudioModeChanges?.onStart { emit(Unit) }
+ ?: flowOf(null),
+ devicesChanges.onStart { emit(Intent()) },
+ ) { controllers, _, _ ->
+ controllers?.let(::findLocalMediaController)
}
- .merge()
- .onStart { emit(Unit) }
- .map { getActiveLocalMediaController() }
.flowOn(backgroundContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
- private fun getActiveLocalMediaController(): MediaController? {
+ private fun findLocalMediaController(
+ controllers: Collection<MediaController>,
+ ): MediaController? {
var localController: MediaController? = null
val remoteMediaSessionLists: MutableList<String> = ArrayList()
- for (controller in mediaSessionManager.getActiveSessions(null)) {
+ for (controller in controllers) {
val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
- val playbackState = controller.playbackState ?: continue
- if (inactivePlaybackStates.contains(playbackState.state)) {
- continue
- }
when (playbackInfo.playbackType) {
MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
if (localController?.packageName.equals(controller.packageName)) {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
index 430d733..7bd43d2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/MediaControllerRepositoryImplTest.kt
@@ -116,7 +116,7 @@
)
)
var mediaController: MediaController? = null
- underTest.activeMediaController
+ underTest.activeLocalMediaController
.onEach { mediaController = it }
.launchIn(backgroundScope)
runCurrent()
@@ -141,7 +141,7 @@
)
)
var mediaController: MediaController? = null
- underTest.activeMediaController
+ underTest.activeLocalMediaController
.onEach { mediaController = it }
.launchIn(backgroundScope)
runCurrent()
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 f9dd04b..0728daf 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 @@
): 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/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
index 43d5453..236aee2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/bottombar/BottomBarModule.kt
@@ -32,7 +32,7 @@
@Binds
@IntoMap
@StringKey(VolumePanelComponents.BOTTOM_BAR)
- fun bindMediaVolumeSliderComponent(component: BottomBarComponent): VolumePanelUiComponent
+ fun bindVolumePanelUiComponent(component: BottomBarComponent): VolumePanelUiComponent
@Binds
@IntoMap
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 3dfe65a..51c008a 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.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 @@
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 @@
@OpenForTesting
open fun querySettings() {
- assertNotMainThread()
+ assert.isNotMainThread()
val result =
try {
val json =
@@ -313,7 +314,7 @@
@OpenForTesting
open fun applySettings(value: ClockSettings?) {
- assertNotMainThread()
+ assert.isNotMainThread()
try {
value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis())
@@ -339,16 +340,6 @@
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 @@
}
scope.launch(mainDispatcher) {
- assertMainThread()
+ assert.isMainThread()
isClockChanged.set(false)
clockChangeListeners.forEach { it.onCurrentClockChanged() }
}
@@ -371,7 +362,7 @@
}
scope.launch(mainDispatcher) {
- assertMainThread()
+ assert.isMainThread()
isClockListChanged.set(false)
clockChangeListeners.forEach { it.onAvailableClocksChanged() }
}
@@ -585,7 +576,7 @@
* 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 @@
* 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 0b320a2..ef2b6f0 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.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 @@
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 @@
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
sceneInteractorProvider = { sceneInteractor },
)
}
@@ -188,6 +191,49 @@
}
@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 83782e2..837a9db 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.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,49 +44,69 @@
@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,
)
-
- keyguardRepository.setKeyguardAlpha(0.1f)
- assertThat(alpha).isEqualTo(0.1f)
- keyguardRepository.setKeyguardAlpha(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)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ enterFromTopAnimationAlpha.value = 0.5f
+ assertThat(alpha).isEqualTo(0.5f)
+
+ enterFromTopAnimationAlpha.value = 1f
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ @Test
+ fun alpha_WhenGoneToDozing() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+ assertThat(alpha).isEqualTo(0f)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ )
+ assertThat(alpha).isEqualTo(1f)
}
@Test
@@ -92,17 +114,53 @@
testScope.runTest {
val alpha by collectLastValue(underTest.alpha)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- testScope = testScope,
+ 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,
+ )
)
- keyguardRepository.setKeyguardAlpha(0.1f)
- assertThat(alpha).isEqualTo(0f)
- keyguardRepository.setKeyguardAlpha(0.5f)
- assertThat(alpha).isEqualTo(0f)
- keyguardRepository.setKeyguardAlpha(1f)
- assertThat(alpha).isEqualTo(0f)
+ 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 d52696a..74fa465 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 @@
clockControllerProvider = { clockController },
)
private val burnInFlow = MutableStateFlow(BurnInModel())
- private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
@Before
fun setUp() {
@@ -74,8 +73,6 @@
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 @@
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 bf1d76f..e04cbfd 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 @@
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/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioModeInteractorTest.kt
similarity index 88%
rename from packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioModeInteractorTest.kt
index 4dbf865..fe34361 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.settingslib.volume.domain.interactor
+package com.android.systemui.volume.domain.interactor
import android.media.AudioManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.BaseTest
-import com.android.settingslib.volume.data.repository.FakeAudioRepository
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.volume.data.repository.FakeAudioRepository
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -34,7 +35,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
-class AudioModeInteractorTest : BaseTest() {
+class AudioModeInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
private val fakeAudioRepository = FakeAudioRepository()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
new file mode 100644
index 0000000..ec37925
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain
+
+import android.media.AudioManager
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.audioModeInteractor
+import com.android.systemui.volume.audioRepository
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaController
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: MediaOutputAvailabilityCriteria
+
+ @Before
+ fun setup() {
+ with(kosmos) {
+ whenever(mediaController.packageName).thenReturn("test.pkg")
+ whenever(mediaController.sessionToken).thenReturn(MediaSession.Token(0, mock {}))
+ whenever(mediaController.playbackState).thenReturn(PlaybackState.Builder().build())
+
+ mediaControllerRepository.setActiveLocalMediaController(mediaController)
+
+ underTest = MediaOutputAvailabilityCriteria(mediaOutputInteractor, audioModeInteractor)
+ }
+ }
+
+ @Test
+ fun notInCallAndHasDevices_isAvailable_true() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setMode(AudioManager.MODE_NORMAL)
+ localMediaRepository.updateMediaDevices(listOf(mock {}))
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
+ }
+ }
+ }
+ @Test
+ fun inCallAndHasDevices_isAvailable_false() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setMode(AudioManager.MODE_IN_CALL)
+ localMediaRepository.updateMediaDevices(listOf(mock {}))
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun notInCallAndHasDevices_isAvailable_false() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setMode(AudioManager.MODE_NORMAL)
+ localMediaRepository.updateMediaDevices(emptyList())
+
+ val isAvailable by collectLastValue(underTest.isAvailable())
+ runCurrent()
+
+ assertThat(isAvailable).isFalse()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 91e6b62..ee260e1 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -421,9 +421,11 @@
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 878a5d8..a0f15ef 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.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 @@
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 3397906..0bd44f0 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 @@
@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 @@
}
}
+
+ 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 @@
() -> 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 31aadf5..b0cc3bd 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.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 @@
(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 @@
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 2417fe9..a37d916 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.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 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 @@
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 @@
// 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 @@
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 @@
// 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 b5ca79e7..63fd608 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 @@
} 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 e20f570..7593ac2 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 @@
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 @@
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 1da0a0e..57e9ac7 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 @@
}
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 22d11d0..405d1d4 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.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 @@
bouncerRepository: KeyguardBouncerRepository,
configurationInteractor: ConfigurationInteractor,
shadeRepository: ShadeRepository,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
sceneInteractorProvider: Provider<SceneInteractor>,
) {
// TODO(b/296118689): move to a repository
@@ -233,8 +235,33 @@
/** 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 310f13d..d1fd719 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 @@
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 1b7a507..5604ef2 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 @@
}
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 d4ea728..f208e85 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 @@
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 8110de2..6fcbf48 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.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 @@
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 @@
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 3a98359..a3888c3 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 @@
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 @@
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 e4610c1..f81941b 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 @@
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 0000000..55a289e
--- /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 83be651..f790d35 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.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 @@
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 @@
// 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 @@
}
/** 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 ce47f3c6..0cfc757 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 @@
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 07c1141..c61b1f5 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.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 @@
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 5879d18..942903b 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 @@
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 284a134..34c9ac9 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.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 @@
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/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 25d89fa..02be0c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -35,10 +35,10 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import javax.inject.Inject
-/**
- * Factory to create [MediaOutputDialog] objects.
- */
-open class MediaOutputDialogFactory @Inject constructor(
+/** Factory to create [MediaOutputDialog] objects. */
+open class MediaOutputDialogFactory
+@Inject
+constructor(
private val context: Context,
private val mediaSessionManager: MediaSessionManager,
private val lbm: LocalBluetoothManager?,
@@ -55,46 +55,93 @@
private val userTracker: UserTracker
) {
companion object {
- private const val INTERACTION_JANK_TAG = "media_output"
+ const val INTERACTION_JANK_TAG = "media_output"
var mediaOutputDialog: MediaOutputDialog? = null
}
/** Creates a [MediaOutputDialog] for the given package. */
open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
- create(packageName, aboveStatusBar, view, includePlaybackAndAppMetadata = true)
+ createWithController(
+ packageName,
+ aboveStatusBar,
+ controller =
+ view?.let {
+ DialogTransitionAnimator.Controller.fromView(
+ it,
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ },
+ )
}
- open fun createDialogForSystemRouting() {
- create(packageName = null, aboveStatusBar = false, includePlaybackAndAppMetadata = false)
+ /** Creates a [MediaOutputDialog] for the given package. */
+ open fun createWithController(
+ packageName: String,
+ aboveStatusBar: Boolean,
+ controller: DialogTransitionAnimator.Controller?,
+ ) {
+ create(
+ packageName,
+ aboveStatusBar,
+ dialogTransitionAnimatorController = controller,
+ includePlaybackAndAppMetadata = true
+ )
+ }
+
+ open fun createDialogForSystemRouting(controller: DialogTransitionAnimator.Controller? = null) {
+ create(
+ packageName = null,
+ aboveStatusBar = false,
+ dialogTransitionAnimatorController = null,
+ includePlaybackAndAppMetadata = false
+ )
}
private fun create(
- packageName: String?,
- aboveStatusBar: Boolean,
- view: View? = null,
- includePlaybackAndAppMetadata: Boolean = true
+ packageName: String?,
+ aboveStatusBar: Boolean,
+ dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
+ includePlaybackAndAppMetadata: Boolean = true
) {
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller = MediaOutputController(
- context, packageName,
- mediaSessionManager, lbm, starter, notifCollection,
- dialogTransitionAnimator, nearbyMediaDevicesManager, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags, userTracker)
+ val controller =
+ MediaOutputController(
+ context,
+ packageName,
+ mediaSessionManager,
+ lbm,
+ starter,
+ notifCollection,
+ dialogTransitionAnimator,
+ nearbyMediaDevicesManager,
+ audioManager,
+ powerExemptionManager,
+ keyGuardManager,
+ featureFlags,
+ userTracker
+ )
val dialog =
- MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller,
- dialogTransitionAnimator, uiEventLogger, includePlaybackAndAppMetadata)
+ MediaOutputDialog(
+ context,
+ aboveStatusBar,
+ broadcastSender,
+ controller,
+ dialogTransitionAnimator,
+ uiEventLogger,
+ includePlaybackAndAppMetadata
+ )
mediaOutputDialog = dialog
// Show the dialog.
- if (view != null) {
- dialogTransitionAnimator.showFromView(
- dialog, view,
- cuj = DialogCuj(
- InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- INTERACTION_JANK_TAG
- )
+ if (dialogTransitionAnimatorController != null) {
+ dialogTransitionAnimator.show(
+ dialog,
+ dialogTransitionAnimatorController,
)
} else {
dialog.show()
diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java
index 3671dd4..b4cc196 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 @@
/**
* 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 2f0fc51..ee602e5 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_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.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 @@
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 @@
}
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 @@
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 2968490..fe45df8 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.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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
}
private void updateKeyguardBottomAreaAlpha() {
+ if (KeyguardShadeMigrationNssl.isEnabled()) {
+ return;
+ }
if (mIsOcclusionTransitionRunning) {
return;
}
@@ -5057,19 +5065,6 @@
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 @@
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 54b6ad7..fb67f7c 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 @@
/**
* 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 @@
/**
* 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 @@
.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 @@
// 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 @@
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 @@
// 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 @@
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 @@
/**
* 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 @@
}
} 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 @@
wasUpdated = false,
shouldHeadsUpEver = shouldHeadsUpEver,
shouldHeadsUpAgain = true,
- isAlerting = false,
+ isHeadsUpEntry = false,
isBinding = false,
)
@@ -418,21 +422,21 @@
/**
* 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 @@
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 @@
}
/**
- * 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 @@
/**
* 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 @@
*/
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 @@
// 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 @@
}
/**
- * 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 @@
}
/**
- * 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 @@
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 @@
/**
* 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 20fae88..c90acee 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 @@
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 boolean hasPulsingNotifications() {
- return mPulsing && mHasAlertEntries;
+ return mPulsing && mHasHeadsUpEntries;
}
public void setPulsing(boolean hasPulsing) {
@@ -716,8 +716,8 @@
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 aa9d3b2..933a780 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 @@
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 ff00cb3..476b054 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.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 @@
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 @@
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 f9702dd..a39bfe0 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 @@
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 a7352be..420701f 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 @@
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 eb08f37..eba3162 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 @@
*/
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 87dfc99..58b82f1 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 @@
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 androidx.annotation.NonNull;
+import com.android.internal.camera.flags.Flags;
+
import java.util.Set;
public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
@@ -102,6 +107,13 @@
}
@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/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
index ab76d45..9f99e97 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt
@@ -24,6 +24,9 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactoryImpl
+import dagger.Binds
import dagger.Module
import dagger.Provides
import kotlin.coroutines.CoroutineContext
@@ -32,6 +35,11 @@
@Module
interface MediaDevicesModule {
+ @Binds
+ fun bindLocalMediaRepositoryFactory(
+ impl: LocalMediaRepositoryFactoryImpl
+ ): LocalMediaRepositoryFactory
+
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 0a1ee24..1f52260 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -26,7 +26,12 @@
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-class LocalMediaRepositoryFactory
+interface LocalMediaRepositoryFactory {
+
+ fun create(packageName: String?): LocalMediaRepository
+}
+
+class LocalMediaRepositoryFactoryImpl
@Inject
constructor(
private val intentsReceiver: AudioManagerIntentsReceiver,
@@ -34,9 +39,9 @@
private val localMediaManagerFactory: LocalMediaManagerFactory,
@Application private val coroutineScope: CoroutineScope,
@Background private val backgroundCoroutineContext: CoroutineContext,
-) {
+) : LocalMediaRepositoryFactory {
- fun create(packageName: String?): LocalMediaRepository =
+ override fun create(packageName: String?): LocalMediaRepository =
LocalMediaRepositoryImpl(
intentsReceiver,
localMediaManagerFactory.create(packageName),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
new file mode 100644
index 0000000..020ec64
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteria.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain
+
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Determines if the Media Output Volume Panel component is available. */
+@VolumePanelScope
+class MediaOutputAvailabilityCriteria
+@Inject
+constructor(
+ private val mediaOutputInteractor: MediaOutputInteractor,
+ private val audioModeInteractor: AudioModeInteractor,
+) : ComponentAvailabilityCriteria {
+
+ override fun isAvailable(): Flow<Boolean> {
+ return combine(mediaOutputInteractor.mediaDevices, audioModeInteractor.isOngoingCall) {
+ devices,
+ isOngoingCall ->
+ !isOngoingCall && devices.isNotEmpty()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
new file mode 100644
index 0000000..170b32c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+
+/** User actions interactor for Media Output Volume Panel component. */
+@VolumePanelScope
+class MediaOutputActionsInteractor
+@Inject
+constructor(
+ private val mediaOutputDialogFactory: MediaOutputDialogFactory,
+ private val activityStarter: ActivityStarter,
+) {
+
+ fun onDeviceClick(expandable: Expandable) {
+ activityStarter.startActivity(
+ Intent(Settings.ACTION_BLUETOOTH_SETTINGS),
+ true,
+ expandable.activityTransitionController(),
+ )
+ }
+
+ fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
+ when (session) {
+ is MediaDeviceSession.Active -> {
+ mediaOutputDialogFactory.createWithController(
+ session.packageName,
+ false,
+ expandable.dialogController()
+ )
+ }
+ is MediaDeviceSession.Inactive -> {
+ mediaOutputDialogFactory.createDialogForSystemRouting(expandable.dialogController())
+ }
+ else -> {
+ /* do nothing */
+ }
+ }
+ }
+
+ private fun Expandable.dialogController(): DialogTransitionAnimator.Controller? {
+ return dialogTransitionController(
+ cuj =
+ DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ MediaOutputDialogFactory.INTERACTION_JANK_TAG
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 6c456f9..7126b23 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -17,10 +17,14 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
import android.content.pm.PackageManager
+import android.media.session.MediaController
+import android.os.Handler
import android.util.Log
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import com.android.settingslib.volume.data.repository.MediaControllerChange
import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import com.android.settingslib.volume.data.repository.stateChanges
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
@@ -30,14 +34,20 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
+/** Provides observable models about the current media session state. */
@OptIn(ExperimentalCoroutinesApi::class)
@VolumePanelScope
class MediaOutputInteractor
@@ -47,32 +57,43 @@
private val packageManager: PackageManager,
@VolumePanelScope private val coroutineScope: CoroutineScope,
@Background private val backgroundCoroutineContext: CoroutineContext,
+ @Background private val backgroundHandler: Handler,
mediaControllerRepository: MediaControllerRepository
) {
- val mediaDeviceSession: Flow<MediaDeviceSession> =
- mediaControllerRepository.activeMediaController.mapNotNull { mediaController ->
- if (mediaController == null) {
- MediaDeviceSession.Inactive
- } else {
+ /** Current [MediaDeviceSession]. Emits when the session playback changes. */
+ val mediaDeviceSession: StateFlow<MediaDeviceSession> =
+ mediaControllerRepository.activeLocalMediaController
+ .flatMapLatest { it?.mediaDeviceSession() ?: flowOf(MediaDeviceSession.Inactive) }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSession.Inactive)
+
+ private fun MediaController.mediaDeviceSession(): Flow<MediaDeviceSession> {
+ return stateChanges(backgroundHandler)
+ .filter { it is MediaControllerChange.PlaybackStateChanged }
+ .map {
MediaDeviceSession.Active(
- appLabel = getApplicationLabel(mediaController.packageName)
- ?: return@mapNotNull null,
- packageName = mediaController.packageName,
- sessionToken = mediaController.sessionToken,
+ appLabel = getApplicationLabel(packageName)
+ ?: return@map MediaDeviceSession.Inactive,
+ packageName = packageName,
+ sessionToken = sessionToken,
+ playbackState = playbackState,
)
}
- }
- private val localMediaRepository: Flow<LocalMediaRepository> =
+ }
+
+ private val localMediaRepository: SharedFlow<LocalMediaRepository> =
mediaDeviceSession
.map { (it as? MediaDeviceSession.Active)?.packageName }
.distinctUntilChanged()
.map { localMediaRepositoryFactory.create(it) }
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 1)
+ .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1)
+ /** Currently connected [MediaDevice]. */
val currentConnectedDevice: Flow<MediaDevice?> =
localMediaRepository.flatMapLatest { it.currentConnectedDevice }
+ /** A list of available [MediaDevice]s. */
val mediaDevices: Flow<Collection<MediaDevice>> =
localMediaRepository.flatMapLatest { it.mediaDevices }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
index f250308..71df8e5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.model
import android.media.session.MediaSession
+import android.media.session.PlaybackState
/** Represents media playing on the connected device. */
sealed interface MediaDeviceSession {
@@ -26,6 +27,7 @@
val appLabel: CharSequence,
val packageName: String,
val sessionToken: MediaSession.Token,
+ val playbackState: PlaybackState?,
) : MediaDeviceSession
/** Media is not playing. */
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 43f7c60..2c1a87d 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.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.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.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 @@
@Before
fun setup() {
+ mSetFlagsRule.disableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ mSetFlagsRule.disableFlags(FLAG_CONSTRAINT_BP)
displayRepository = FakeDisplayRepository()
displayStateInteractor =
@@ -394,6 +399,19 @@
}
@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 f3807e4..c381749 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 @@
}
@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 17e4e0f..61fee16 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 @@
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
+ KeyguardTransitionInteractor keyguardTransitionInteractor =
+ mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
keyguardRepository,
new FakeCommandQueue(),
@@ -194,12 +196,10 @@
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 2f765d5..061f88e 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 @@
mKosmos.getDeviceUnlockedInteractor());
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
+ KeyguardTransitionInteractor keyguardTransitionInteractor =
+ mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
mKeyguardRepository,
new FakeCommandQueue(),
@@ -221,11 +223,9 @@
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 64fd80d..74d0173 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.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 @@
@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 @@
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 fb105e2..1396a43 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 @@
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 @@
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 9055ba4..2da88e9 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.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 @@
@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 @@
init {
kosmos.aodBurnInViewModel = aodBurnInViewModel
+ kosmos.splitShadeStateController = splitShadeStateController
}
val testScope = kosmos.testScope
val configurationRepository = kosmos.fakeConfigurationRepository
@@ -93,7 +97,7 @@
@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 @@
@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 @@
@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 @@
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 @@
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 @@
@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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
// 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 76c9740..56fc7b9 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.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 @@
MockitoAnnotations.initMocks(this);
when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
-
+ KeyguardTransitionInteractor keyguardTransitionInteractor =
+ mKosmos.getKeyguardTransitionInteractor();
mKeyguardInteractor = new KeyguardInteractor(
mKeyguardRepository,
mCommandQueue,
@@ -171,6 +173,7 @@
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 b7560ad..1687ccb 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_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.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.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 @@
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 @@
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 ca0e526..76913e8 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.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 @@
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 97bd96d..d87df0a 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 @@
mKosmos.getDeviceUnlockedInteractor());
FakeSceneContainerFlags sceneContainerFlags = new FakeSceneContainerFlags();
+ KeyguardTransitionInteractor keyguardTransitionInteractor =
+ mKosmos.getKeyguardTransitionInteractor();
KeyguardInteractor keyguardInteractor = new KeyguardInteractor(
keyguardRepository,
new FakeCommandQueue(),
@@ -428,11 +430,9 @@
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 0bba36b..3893a9b7 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.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.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 @@
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 @@
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 58d99b5..5140a9f 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 @@
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 0c38fd9..6df7493 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 @@
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 6b89e0f..9fb3284 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 @@
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 0000000..4daf460
--- /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 24bb9c5..4939237b 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 @@
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
- aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
- lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
+ aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
+ lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
+ lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+ lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
+ 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/media/MediaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
new file mode 100644
index 0000000..e1b1966
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.util.mockito.mock
+
+var Kosmos.mediaOutputDialogFactory: MediaOutputDialogFactory by Kosmos.Fixture { mock {} }
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 30d4105..8882de0 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.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 @@
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/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
new file mode 100644
index 0000000..3f20df3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume
+
+import android.content.packageManager
+import android.content.pm.ApplicationInfo
+import android.media.session.MediaController
+import android.os.Handler
+import android.testing.TestableLooper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.mediaOutputDialogFactory
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.volume.data.repository.FakeLocalMediaRepository
+import com.android.systemui.volume.data.repository.FakeMediaControllerRepository
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.FakeLocalMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
+
+var Kosmos.mediaController: MediaController by Kosmos.Fixture { mock {} }
+
+val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() }
+val Kosmos.localMediaRepositoryFactory: LocalMediaRepositoryFactory by
+ Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
+
+val Kosmos.mediaOutputActionsInteractor by
+ Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
+val Kosmos.mediaOutputInteractor by
+ Kosmos.Fixture {
+ MediaOutputInteractor(
+ localMediaRepositoryFactory,
+ packageManager.apply {
+ val appInfo: ApplicationInfo = mock {
+ whenever(loadLabel(any())).thenReturn("test_label")
+ }
+ whenever(getApplicationInfo(any(), any<Int>())).thenReturn(appInfo)
+ },
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ Handler(TestableLooper.get(testCase).looper),
+ mediaControllerRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeKosmos.kt
new file mode 100644
index 0000000..5e1f85c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/VolumeKosmos.kt
@@ -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 com.android.systemui.volume
+
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.volume.data.repository.FakeAudioRepository
+
+val Kosmos.audioRepository by Kosmos.Fixture { FakeAudioRepository() }
+val Kosmos.audioModeInteractor by Kosmos.Fixture { AudioModeInteractor(audioRepository) }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
similarity index 96%
rename from packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
index dddf8e82..fed3e17 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.settingslib.volume.data.repository
+package com.android.systemui.volume.data.repository
import android.media.AudioDeviceInfo
+import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
similarity index 71%
rename from packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
index 642b72c..7835fc8 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeLocalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeLocalMediaRepository.kt
@@ -1,23 +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
+ * 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
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-package com.android.settingslib.volume.data.repository
+package com.android.systemui.volume.data.repository
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.model.RoutingSession
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt
new file mode 100644
index 0000000..6d52e52
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeMediaControllerRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.data.repository
+
+import android.media.session.MediaController
+import com.android.settingslib.volume.data.repository.MediaControllerRepository
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeMediaControllerRepository : MediaControllerRepository {
+
+ private val mutableActiveLocalMediaController = MutableStateFlow<MediaController?>(null)
+ override val activeLocalMediaController: StateFlow<MediaController?> =
+ mutableActiveLocalMediaController.asStateFlow()
+
+ fun setActiveLocalMediaController(controller: MediaController?) {
+ mutableActiveLocalMediaController.value = controller
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
new file mode 100644
index 0000000..1b3480c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.data.repository
+
+import com.android.settingslib.volume.data.repository.LocalMediaRepository
+
+class FakeLocalMediaRepositoryFactory(
+ val provider: (packageName: String?) -> LocalMediaRepository
+) : LocalMediaRepositoryFactory {
+
+ override fun create(packageName: String?): LocalMediaRepository = provider(packageName)
+}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 35ce481..132804f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -24,6 +24,46 @@
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 @@
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 d84cb67..4b2f968 100644
--- a/ravenwood/api-maintainers.md
+++ b/ravenwood/api-maintainers.md
@@ -82,7 +82,7 @@
```
@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
similarity index 98%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/CursorWindow_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/CursorWindow_host.java
index eba9910..f38d565 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
similarity index 97%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/EventLog_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/EventLog_host.java
index 6480cfc..55d4ffb 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
similarity index 97%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Log_host.java
index cdfa302..5930a14 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
similarity index 99%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongArrayMultiStateCounter_host.java
index 4d39d88..7414110 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
similarity index 98%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongMultiStateCounter_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/LongMultiStateCounter_host.java
index a5d0fc6..9486651 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
similarity index 97%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/MessageQueue_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/MessageQueue_host.java
index 65da4a1..5e81124 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
similarity index 98%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/ParcelFileDescriptor_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/ParcelFileDescriptor_host.java
index 0ebaac60..2d79914 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
similarity index 99%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/Parcel_host.java
index d63bff6..81ad31e 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
similarity index 98%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/nativesubstitution/SystemProperties_host.java
index 2f6a361..eba6c8b 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
similarity index 96%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java
rename to ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index fbcc648..1e12030 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 @@
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
similarity index 100%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/android/system/ErrnoException.java
rename to 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
similarity index 100%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
rename to 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
similarity index 100%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java
rename to 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
similarity index 100%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/io/IoUtils.java
rename to 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
similarity index 100%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java
rename to 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
similarity index 100%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/HexEncoding.java
rename to 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
similarity index 100%
rename from tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java
rename to ravenwood/runtime-helper-src/libcore-fake/libcore/util/SneakyThrow.java
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 6ce471e..fff283d 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -16,10 +16,9 @@
package com.android.server;
+import static android.permission.flags.Flags.sensitiveNotificationAppProtection;
import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS;
-
import static com.android.internal.util.Preconditions.checkNotNull;
-import static com.android.server.notification.Flags.sensitiveNotificationAppProtection;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index a341b4a..2e14abb 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -263,6 +263,10 @@
// 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 @@
return mAllowedAssociations;
}
+ public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+ return mAllowlistCameraPrivacy;
+ }
+
public ArraySet<String> getBugreportWhitelistedPackages() {
return mBugreportWhitelistedPackages;
}
@@ -1062,6 +1070,22 @@
}
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 c857235..1dc384d 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 @@
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 5c95d43..145b213 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.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.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 @@
*/
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 @@
}
}
});
+ mSensorPrivacyManager = SensorPrivacyManager.getInstance(mContext);
}
@VisibleForTesting
@@ -4642,6 +4647,10 @@
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 @@
}
}
+ 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 7fb3e00..9a76ebd 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 @@
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_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 @@
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 @@
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 @@
setCurrentLskfBasedProtectorId(newProtectorId, userId);
LockPatternUtils.invalidateCredentialTypeCache();
synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);
+ sendMainUserCredentialChangedNotificationIfNeeded(userId);
setUserPasswordMetrics(credential, userId);
mUnifiedProfilePasswordCache.removePassword(userId);
@@ -3071,6 +3080,24 @@
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 6d123cc..158d444 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.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 @@
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 @@
}
}
+ 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 bbb19e3..1546895 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 @@
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 @@
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 @@
@Override // Binder call
public void setLaunchCookie(LaunchCookie launchCookie) {
setLaunchCookie_enforcePermission();
+ setLaunchCookieInternal(launchCookie);
+ }
+
+ @VisibleForTesting void setLaunchCookieInternal(LaunchCookie launchCookie) {
mLaunchCookie = launchCookie;
}
@@ -1199,6 +1214,10 @@
@Override // Binder call
public LaunchCookie getLaunchCookie() {
getLaunchCookie_enforcePermission();
+ return getLaunchCookieInternal();
+ }
+
+ @VisibleForTesting LaunchCookie getLaunchCookieInternal() {
return mLaunchCookie;
}
@@ -1206,6 +1225,11 @@
@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 @@
@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 e349fa3..babb6c2 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_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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
newColor = oldAttr.iconColor;
}
- return new NotificationAttributes(oldAttr.flags, newIcon, newColor);
+ return new NotificationAttributes(oldAttr.flags, newIcon, newColor, newAttr.visibility);
}
/**
@@ -358,17 +371,20 @@
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 @@
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 ea4e67a..d751186 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 @@
}
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 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 @@
// 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 @@
.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/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 722654a..53244f9 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -50,15 +50,6 @@
}
flag {
- name: "sensitive_notification_app_protection"
- namespace: "systemui"
- description: "This flag controls the sensitive notification app protections while screen sharing"
- bug: "312784351"
- # Referenced in WM where WM starts before DeviceConfig
- is_fixed_read_only: true
-}
-
-flag {
name: "notification_reduce_messagequeue_usage"
namespace: "systemui"
description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 295528e..f9d8112 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 @@
}
@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 d644235..449e9ab 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 @@
@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 a9d2858..c2f74a8 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 @@
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 @@
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 59766ec..f8c678a 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.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 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_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.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 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
* @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 @@
== 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 @@
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 @@
* 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 @@
// 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 @@
}
@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 @@
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 @@
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 @@
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 9694958..14b13e0 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 @@
}
}
+ @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 @@
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 3dcb4cf..4b491ce 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 @@
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 d2f6701..4af8c61 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.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 @@
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 @@
}
}
+ 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 62a637e..3077fb8 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 @@
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 9ba4433..2b43203 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.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 @@
@VisibleForTesting
RemoteWearableSensingService mRemoteService;
+ @Nullable private VoiceInteractionManagerInternal mVoiceInteractionManagerInternal;
private ComponentName mComponentName;
private final Object mSecureChannelLock = new Object();
@@ -99,6 +107,15 @@
}
}
+ @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 @@
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 78952fa..00c3026 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 @@
}
@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 a54dd82..3609837 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 @@
*/
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 1f013b9..f83003d 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 @@
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 b65b2b2..2d6f03a 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 @@
// 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 e743172..25a5fca 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 @@
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 401b260..8680436 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.util.ArrayMap;
import android.window.TaskSnapshot;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
/**
@@ -26,25 +28,31 @@
* @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 @@
/** 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 @@
}
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 @@
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/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 33486cc..b69ac1b 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 @@
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 d8e18e4..8b622d2 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 @@
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 817901f..fa2d9bf 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 @@
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 @@
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 @@
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/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f56e50e..6e993b3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1897,7 +1897,7 @@
return true;
}
- if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) {
+ if (android.permission.flags.Flags.sensitiveNotificationAppProtection()) {
if (mWmService.mSensitiveContentPackages
.shouldBlockScreenCaptureForApp(getOwningPackage(), getOwningUid())) {
return true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 105dc88..0ccf810c 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 @@
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 @@
* 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 @@
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 @@
}
}
+ /**
+ * 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 @@
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 @@
writeGlobalPoliciesInner(serializer);
writeEnforcingAdminsInner(serializer);
writeEnforcingAdminSizeInner(serializer);
+ writeMaxPolicySizeInner(serializer);
}
private void writeLocalPoliciesInner(TypedXmlSerializer serializer) throws IOException {
@@ -1886,6 +1914,17 @@
}
}
+ 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 @@
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 @@
}
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 51eee64..e619440 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_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_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_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.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 @@
applyProfileRestrictionsIfDeviceOwnerLocked();
// TODO: Is this the right place to trigger the migration?
- if (shouldMigrateToDevicePolicyEngine()) {
- migratePoliciesToDevicePolicyEngine();
+ if (shouldMigrateV1ToDevicePolicyEngine()) {
+ migrateV1PoliciesToDevicePolicyEngine();
}
+ migratePoliciesToPolicyEngineLocked();
}
maybeStartSecurityLogMonitorOnActivityManagerReady();
break;
@@ -3392,6 +3398,48 @@
}
}
+ @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 @@
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 @@
}
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 @@
}
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 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 @@
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 @@
}
}
+ 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 @@
@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 @@
}
// 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 @@
}
@Override
- public SystemUpdateInfo getPendingSystemUpdate(ComponentName admin) {
- Objects.requireNonNull(admin, "ComponentName is null");
+ 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));
-
+ final CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ }
return mOwners.getSystemUpdateInfo();
}
@@ -17586,19 +17677,32 @@
}
@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 @@
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,26 +17737,36 @@
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);
- } 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));
- }
- }
- }
+ 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 {
+ // A delegate app passes a null admin component, which is expected
+ Preconditions.checkCallAuthorization(
+ isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
+ }
+ return mInjector.securityLogGetLoggingEnabledProperty();
+ }
+ }
}
private void recordSecurityLogRetrievalTime() {
@@ -17727,17 +17841,32 @@
}
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 @@
}
Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
|| areAllUsersAffiliatedWithDeviceLocked());
- }
- if (!mInjector.securityLogGetLoggingEnabledProperty()) {
- return null;
+ if (!mInjector.securityLogGetLoggingEnabledProperty()) {
+ return null;
+ }
}
recordSecurityLogRetrievalTime();
@@ -17758,7 +17887,7 @@
.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 @@
}
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 @@
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 @@
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 @@
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 @@
});
}
- 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 @@
});
}
+ /**
+ * 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 @@
}
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 bb275e45..c5a9888 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 @@
}
}
+ 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 37d4f95..d9fef10 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 @@
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 @@
private final PolicyPathProvider mPathProvider;
boolean mMigratedToPolicyEngine = false;
+ boolean mSecurityLoggingMigrated = false;
OwnersData(PolicyPathProvider pathProvider) {
mPathProvider = pathProvider;
@@ -397,6 +400,9 @@
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 @@
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 b09908e..3474db3 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 @@
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 @@
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 506dbe8..4aaefa6 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.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 @@
}
}
+ 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 939a3dc..7a4454b 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 @@
/** 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 @@
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 @@
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/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1d89d17..14e4481 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -3014,7 +3014,7 @@
t.traceEnd();
}
- if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()
+ if (android.permission.flags.Flags.sensitiveNotificationAppProtection()
|| android.view.flags.Flags.sensitiveContentAppProtection()) {
t.traceBegin("StartSensitiveContentProtectionManager");
mSystemServiceManager.startService(SensitiveContentProtectionManagerService.class);
diff --git a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
index f15e533..bd86fe2 100644
--- a/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
+++ b/services/tests/PackageManagerServiceTests/appenumeration/Android.bp
@@ -32,6 +32,8 @@
"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 4012d8e..c615823 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 @@
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 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 @@
@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/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
index 9473e57..c298d51 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -18,7 +18,7 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.server.notification.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
+import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
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 6986cab..e59b5ea 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 @@
}
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 ee076c6..296d2cb 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 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 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 @@
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 7053597..4b22652 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.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 @@
}
@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 @@
}
@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 @@
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 fa3c7a4c..c01d0f6 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 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 @@
}).when(mPersistentDataBlockManager).getFrpCredentialHandle();
}
+ void setTestFactoryResetProtectionState(boolean active) {
+ mIsFactoryResetProtectionActive = active;
+ }
+
@Override
File getChildProfileLockFile(int userId) {
return remapToStorageDir(super.getChildProfileLockFile(userId));
@@ -101,4 +106,9 @@
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 abd3abe..aefa6de 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.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 @@
@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 @@
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 @@
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 @@
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 @@
mClock.fastForward(projection.mDefaultTimeoutMs + 10);
// Immediate timeout - so no longer valid.
- assertThat(projection.isValid()).isFalse();
+ assertThat(projection.isValidInternal()).isFalse();
}
@Test
@@ -452,10 +448,10 @@
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 @@
// 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 @@
// Second start - so not valid.
projection.start(mIMediaProjectionCallback);
- assertThat(projection.isValid()).isFalse();
+ assertThat(projection.isValidInternal()).isFalse();
}
@Test
@@ -627,7 +623,7 @@
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 @@
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 @@
@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 @@
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 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 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 d50affb..a0f2395 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 @@
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.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 @@
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 @@
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 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 @@
});
}
-
- /**
- * 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 5eb76e3..f077914 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_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 @@
@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 @@
}
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 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 6aacfd7..94d24a9 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_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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 f9ba33b..6e5c180 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 @@
}
@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/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 887e5ee..60dfe6f 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 @@
@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 @@
// 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 @@
// 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 c84fe08..42004c3 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.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 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 @@
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 06d30fc..29f48b8 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 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 df5f3d1..7432537 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 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/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f02dd3f..8b0fc2c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.ROTATION_0;
@@ -55,7 +56,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.notification.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW;
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DetectorSession.java
index 20a0850..368a96b 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.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.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 @@
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 @@
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@Nullable PersistableBundle options,
- IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
+ IMicrophoneHotwordDetectionVoiceInteractionCallback callback,
+ boolean shouldCloseAudioStreamWithDelayOnDetect) {
if (DEBUG) {
Slog.d(TAG, "#handleExternalSourceHotwordDetectionLocked");
}
@@ -482,12 +562,22 @@
// 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 @@
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 @@
}
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 @@
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 @@
+ 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 cd390a1..f1f5458 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.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 @@
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 4cdec70..ecb0f96 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.voiceinteraction;
+
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -74,6 +75,7 @@
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;
@@ -319,6 +321,46 @@
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
@@ -1706,7 +1748,10 @@
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 7538142..84b36d5 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.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 @@
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 c62db1ea..13b5f0d 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 14230fe..0fb4f90 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.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 @@
@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 @@
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 6b65b40e..e81f5f8 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 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 7330ec1..e700bc2 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 @@
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 @@
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 @@
}
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/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index c6dd29c..30333da 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -54,6 +54,7 @@
// 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 @@
"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 @@
"guava",
],
jarjar_rules: "jarjar-rules.txt",
- visibility: ["//visibility:public"],
}
// Host-side stub generator tool.
@@ -152,34 +152,3 @@
"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 3c55237..ce856cd 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.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 @@
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 @@
}
}.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 @@
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())