diff options
101 files changed, 3888 insertions, 763 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 497619ae0613..ad849002cca1 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -21,6 +21,7 @@ aconfig_declarations_group { java_aconfig_libraries: [ // !!! KEEP THIS LIST ALPHABETICAL !!! "aconfig_mediacodec_flags_java_lib", + "aconfig_settingslib_flags_java_lib", "aconfig_trade_in_mode_flags_java_lib", "android-sdk-flags-java", "android.adaptiveauth.flags-aconfig-java", @@ -1757,3 +1758,19 @@ cc_aconfig_library { ], min_sdk_version: "apex_inherit", } + +// Settings Lib +aconfig_declarations { + name: "aconfig_settingslib_flags", + package: "com.android.settingslib.flags", + container: "system", + srcs: [ + "packages/SettingsLib/aconfig/settingslib.aconfig", + ], +} + +java_aconfig_library { + name: "aconfig_settingslib_flags_java_lib", + aconfig_declarations: "aconfig_settingslib_flags", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/Android.bp b/Android.bp index 26d0d65f329c..9cb3067096cc 100644 --- a/Android.bp +++ b/Android.bp @@ -220,7 +220,7 @@ java_library { "android.hardware.contexthub-V1.0-java", "android.hardware.contexthub-V1.1-java", "android.hardware.contexthub-V1.2-java", - "android.hardware.contexthub-V3-java", + "android.hardware.contexthub-V4-java", "android.hardware.gnss-V1.0-java", "android.hardware.gnss-V2.1-java", "android.hardware.health-V1.0-java-constants", diff --git a/core/api/current.txt b/core/api/current.txt index ead655426bf9..664b3dd125ef 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -263,6 +263,7 @@ package android { field public static final String READ_SMS = "android.permission.READ_SMS"; field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS"; field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS"; + field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String READ_SYSTEM_PREFERENCES = "android.permission.READ_SYSTEM_PREFERENCES"; field public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL"; field public static final String REBOOT = "android.permission.REBOOT"; field public static final String RECEIVE_BOOT_COMPLETED = "android.permission.RECEIVE_BOOT_COMPLETED"; @@ -313,6 +314,7 @@ package android { field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW"; field public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR"; field public static final String TURN_SCREEN_ON = "android.permission.TURN_SCREEN_ON"; + field @FlaggedApi("android.app.enable_tv_implicit_enter_pip_restriction") public static final String TV_IMPLICIT_ENTER_PIP = "android.permission.TV_IMPLICIT_ENTER_PIP"; field public static final String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT"; field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"; @@ -334,6 +336,7 @@ package android { field public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS"; field public static final String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS"; field public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS"; + field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES"; field public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL"; } @@ -8837,7 +8840,7 @@ package android.app.appfunctions { field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 - field public static final String PROPERTY_RETURN_VALUE = "returnValue"; + field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8 field public static final int RESULT_CANCELLED = 2001; // 0x7d1 field public static final int RESULT_DENIED = 1000; // 0x3e8 @@ -19777,6 +19780,9 @@ package android.hardware.camera2 { field public static final int EDGE_MODE_HIGH_QUALITY = 2; // 0x2 field public static final int EDGE_MODE_OFF = 0; // 0x0 field public static final int EDGE_MODE_ZERO_SHUTTER_LAG = 3; // 0x3 + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1; // 0x1 + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2; // 0x2 + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0; // 0x0 field public static final int FLASH_MODE_OFF = 0; // 0x0 field public static final int FLASH_MODE_SINGLE = 1; // 0x1 field public static final int FLASH_MODE_TORCH = 2; // 0x2 @@ -20076,6 +20082,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_CURRENT_TYPE; + field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_NIGHT_MODE_INDICATOR; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_STRENGTH; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STATE; @@ -23973,6 +23980,7 @@ package android.media { field public static final String KEY_MPEGH_COMPATIBLE_SETS = "mpegh-compatible-sets"; field public static final String KEY_MPEGH_PROFILE_LEVEL_INDICATION = "mpegh-profile-level-indication"; field public static final String KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT = "mpegh-reference-channel-layout"; + field @FlaggedApi("android.media.codec.num_input_slots") public static final String KEY_NUM_SLOTS = "num-slots"; field public static final String KEY_OPERATING_RATE = "operating-rate"; field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth"; field public static final String KEY_PCM_ENCODING = "pcm-encoding"; @@ -40371,7 +40379,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean); } - @FlaggedApi("android.security.keystore_grant_api") public class KeyStoreManager { + @FlaggedApi("android.security.keystore_grant_api") public final class KeyStoreManager { method @NonNull public java.util.List<java.security.cert.X509Certificate> getGrantedCertificateChainFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException; method @NonNull public java.security.Key getGrantedKeyFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException; method @NonNull public java.security.KeyPair getGrantedKeyPairFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException; @@ -41999,6 +42007,174 @@ package android.service.restrictions { } +package android.service.settings.preferences { + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getPreferenceKey(); + method @NonNull public String getScreenKey(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueRequest> CREATOR; + } + + public static final class GetValueRequest.Builder { + ctor public GetValueRequest.Builder(@NonNull String, @NonNull String); + method @NonNull public android.service.settings.preferences.GetValueRequest build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueResult implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.service.settings.preferences.SettingsPreferenceMetadata getMetadata(); + method public int getResultCode(); + method @Nullable public android.service.settings.preferences.SettingsPreferenceValue getValue(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueResult> CREATOR; + field public static final int RESULT_DISALLOW = 4; // 0x4 + field public static final int RESULT_INTERNAL_ERROR = 6; // 0x6 + field public static final int RESULT_INVALID_REQUEST = 5; // 0x5 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_REQUIRE_APP_PERMISSION = 3; // 0x3 + field public static final int RESULT_UNAVAILABLE = 2; // 0x2 + field public static final int RESULT_UNSUPPORTED = 1; // 0x1 + } + + public static final class GetValueResult.Builder { + ctor public GetValueResult.Builder(int); + method @NonNull public android.service.settings.preferences.GetValueResult build(); + method @NonNull public android.service.settings.preferences.GetValueResult.Builder setMetadata(@Nullable android.service.settings.preferences.SettingsPreferenceMetadata); + method @NonNull public android.service.settings.preferences.GetValueResult.Builder setValue(@Nullable android.service.settings.preferences.SettingsPreferenceValue); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataRequest 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.service.settings.preferences.MetadataRequest> CREATOR; + } + + public static final class MetadataRequest.Builder { + ctor public MetadataRequest.Builder(); + method @NonNull public android.service.settings.preferences.MetadataRequest build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataResult implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata> getMetadataList(); + method public int getResultCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataResult> CREATOR; + field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_UNSUPPORTED = 1; // 0x1 + } + + public static final class MetadataResult.Builder { + ctor public MetadataResult.Builder(int); + method @NonNull public android.service.settings.preferences.MetadataResult build(); + method @NonNull public android.service.settings.preferences.MetadataResult.Builder setMetadataList(@NonNull java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata>); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public String getPreferenceKey(); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue getPreferenceValue(); + method @NonNull public String getScreenKey(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueRequest> CREATOR; + } + + public static final class SetValueRequest.Builder { + ctor public SetValueRequest.Builder(@NonNull String, @NonNull String, @NonNull android.service.settings.preferences.SettingsPreferenceValue); + method @NonNull public android.service.settings.preferences.SetValueRequest build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueResult implements android.os.Parcelable { + method public int describeContents(); + method public int getResultCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueResult> CREATOR; + field public static final int RESULT_DISABLED = 2; // 0x2 + field public static final int RESULT_DISALLOW = 7; // 0x7 + field public static final int RESULT_INTERNAL_ERROR = 9; // 0x9 + field public static final int RESULT_INVALID_REQUEST = 8; // 0x8 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_REQUIRE_APP_PERMISSION = 5; // 0x5 + field public static final int RESULT_REQUIRE_USER_CONSENT = 6; // 0x6 + field public static final int RESULT_RESTRICTED = 3; // 0x3 + field public static final int RESULT_UNAVAILABLE = 4; // 0x4 + field public static final int RESULT_UNSUPPORTED = 1; // 0x1 + } + + public static final class SetValueResult.Builder { + ctor public SetValueResult.Builder(int); + method @NonNull public android.service.settings.preferences.SetValueResult build(); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceMetadata implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<java.lang.String> getBreadcrumbs(); + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getKey(); + method @Nullable public android.app.PendingIntent getLaunchIntent(); + method @NonNull public java.util.List<java.lang.String> getReadPermissions(); + method @NonNull public String getScreenKey(); + method @Nullable public String getSummary(); + method @Nullable public String getTitle(); + method @NonNull public java.util.List<java.lang.String> getWritePermissions(); + method public int getWriteSensitivity(); + method public boolean isAvailable(); + method public boolean isEnabled(); + method public boolean isRestricted(); + method public boolean isWritable(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR; + field public static final int INTENT_ONLY = 2; // 0x2 + field public static final int NOT_SENSITIVE = 0; // 0x0 + field public static final int SENSITIVE = 1; // 0x1 + } + + public static final class SettingsPreferenceMetadata.Builder { + ctor public SettingsPreferenceMetadata.Builder(@NonNull String, @NonNull String); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata build(); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setAvailable(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.app.PendingIntent); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setReadPermissions(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setRestricted(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setSummary(@Nullable String); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setTitle(@Nullable String); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritable(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritePermissions(@NonNull java.util.List<java.lang.String>); + method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWriteSensitivity(int); + } + + @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceValue implements android.os.Parcelable { + method public int describeContents(); + method public boolean getBooleanValue(); + method public double getDoubleValue(); + method public long getLongValue(); + method @Nullable public String getStringValue(); + method public int getType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceValue> CREATOR; + field public static final int TYPE_BOOLEAN = 0; // 0x0 + field public static final int TYPE_DOUBLE = 2; // 0x2 + field public static final int TYPE_LONG = 1; // 0x1 + field public static final int TYPE_STRING = 3; // 0x3 + } + + public static final class SettingsPreferenceValue.Builder { + ctor public SettingsPreferenceValue.Builder(int); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build(); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setDoubleValue(double); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setLongValue(long); + method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setStringValue(@Nullable String); + } + +} + package android.service.textservice { public abstract class SpellCheckerService extends android.app.Service { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 7a1c759a3ec4..3fccc17e1bf1 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -30,6 +30,7 @@ import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext; import static java.lang.Character.MIN_VALUE; +import android.Manifest; import android.annotation.AnimRes; import android.annotation.CallSuper; import android.annotation.CallbackExecutor; @@ -3193,6 +3194,16 @@ public class Activity extends ContextThemeWrapper return ActivityTaskManager.getMaxNumPictureInPictureActions(this); } + private boolean isImplicitEnterPipProhibited() { + PackageManager pm = getPackageManager(); + if (android.app.Flags.enableTvImplicitEnterPipRestriction()) { + return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) + && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, + getPackageName()) == PackageManager.PERMISSION_DENIED; + } + return false; + } + /** * @return Whether this device supports picture-in-picture. */ @@ -9192,6 +9203,8 @@ public class Activity extends ContextThemeWrapper } dispatchActivityPreResumed(); + mCanEnterPictureInPicture = true; + mFragments.execPendingActions(); mLastNonConfigurationInstances = null; @@ -9243,6 +9256,11 @@ public class Activity extends ContextThemeWrapper Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:" + mComponent.getClassName()); } + + if (isImplicitEnterPipProhibited()) { + mCanEnterPictureInPicture = false; + } + dispatchActivityPrePaused(); mDoReportFullyDrawn = false; mFragments.dispatchPause(); @@ -9265,6 +9283,10 @@ public class Activity extends ContextThemeWrapper final void performUserLeaving() { onUserInteraction(); + + if (isImplicitEnterPipProhibited()) { + mCanEnterPictureInPicture = false; + } onUserLeaveHint(); } diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java index 41bb62270e9f..1557815a8468 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java @@ -111,8 +111,8 @@ public final class ExecuteAppFunctionRequest implements Parcelable { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - * <p>The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. * * @see AppFunctionManager on how to determine the expected parameters. */ diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java index cdf02e6f5a09..ced4b553d641 100644 --- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java @@ -71,7 +71,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { * * <p>See {@link #getResultDocument} for more information on extracting the return value. */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; + public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; /** * The call was successful. diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig index 9a645192a155..c8455c1f439f 100644 --- a/core/java/android/app/multitasking.aconfig +++ b/core/java/android/app/multitasking.aconfig @@ -8,3 +8,11 @@ flag { description: "Enables PiP UI state callback on entering" bug: "303718131" } + +flag { + name: "enable_tv_implicit_enter_pip_restriction" + is_exported: true + namespace: "tv_system_ui" + description: "Enables restrictions to PiP entry on TV for setAutoEnterEnabled and lifecycle methods" + bug: "283115999" +} diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 86bbd4a57a63..987e2ad768b0 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -4289,6 +4289,39 @@ public abstract class CameraMetadata<TKey> { */ public static final int SYNC_FRAME_NUMBER_UNKNOWN = -2; + // + // Enumeration values for CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + // + + /** + * <p>The camera can't accurately assess the scene's lighting to determine if a Night Mode + * Camera Extension capture would improve the photo. This can happen when the current + * camera configuration doesn't support night mode indicator detection, such as when + * the auto exposure mode is ON_AUTO_FLASH, ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or + * ON_EXTERNAL_FLASH.</p> + * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + */ + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0; + + /** + * <p>The camera has detected lighting conditions that are sufficiently bright. Night + * Mode Camera Extensions is available but may not be able to optimize the camera + * settings to take a higher quality photo.</p> + * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + */ + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1; + + /** + * <p>The camera has detected low-light conditions. It is recommended to use Night Mode + * Camera Extension to optimize the camera settings to take a high-quality photo in + * the dark.</p> + * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR + */ + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2; + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index ae72ca40fc5a..bf3a072ff097 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -6016,6 +6016,38 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { public static final Key<Integer> EXTENSION_STRENGTH = new Key<Integer>("android.extension.strength", int.class); + /** + * <p>Indicates when to activate Night Mode Camera Extension for high-quality + * still captures in low-light conditions.</p> + * <p>Provides awareness to the application when the current scene can benefit from using a + * Night Mode Camera Extension to take a high-quality photo.</p> + * <p>Support for this capture result can be queried via + * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p> + * <p>If the device supports this capability then it will also support + * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} + * and will be available in both + * {@link android.hardware.camera2.CameraCaptureSession sessions} and + * {@link android.hardware.camera2.CameraExtensionSession sessions}.</p> + * <p>The value will be {@code UNKNOWN} in the following auto exposure modes: ON_AUTO_FLASH, + * ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or ON_EXTERNAL_FLASH.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN UNKNOWN}</li> + * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_OFF OFF}</li> + * <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_ON ON}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN + * @see #EXTENSION_NIGHT_MODE_INDICATOR_OFF + * @see #EXTENSION_NIGHT_MODE_INDICATOR_ON + */ + @PublicKey + @NonNull + @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR) + public static final Key<Integer> EXTENSION_NIGHT_MODE_INDICATOR = + new Key<Integer>("android.extension.nightModeIndicator", int.class); + /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~ * End generated code *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/ diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java index e22c263e893d..1cc085658bfa 100644 --- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java +++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java @@ -437,7 +437,7 @@ public class CameraMetadataNative implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public synchronized void writeToParcel(Parcel dest, int flags) { nativeWriteToParcel(dest, mMetadataPtr); } @@ -479,7 +479,7 @@ public class CameraMetadataNative implements Parcelable { return getBase(key); } - public void readFromParcel(Parcel in) { + public synchronized void readFromParcel(Parcel in) { nativeReadFromParcel(in, mMetadataPtr); updateNativeAllocation(); } @@ -592,28 +592,33 @@ public class CameraMetadataNative implements Parcelable { } private <T> T getBase(Key<T> key) { - int tag; - if (key.hasTag()) { - tag = key.getTag(); - } else { - tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName()); - key.cacheTag(tag); - } - byte[] values = readValues(tag); - if (values == null) { - // If the key returns null, use the fallback key if exists. - // This is to support old key names for the newly published keys. - if (key.mFallbackName == null) { - return null; + int tag, nativeType; + byte[] values = null; + synchronized (this) { + if (key.hasTag()) { + tag = key.getTag(); + } else { + tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName()); + key.cacheTag(tag); } - tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName); values = readValues(tag); if (values == null) { - return null; + // If the key returns null, use the fallback key if exists. + // This is to support old key names for the newly published keys. + if (key.mFallbackName == null) { + return null; + } + tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName); + values = readValues(tag); + if (values == null) { + return null; + } } - } - int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); + nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); + } + // This block of code doesn't need to be synchronized since we aren't writing or reading + // from the metadata buffer for this instance of CameraMetadataNative. Marshaler<T> marshaler = getMarshalerForKey(key, nativeType); ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); return marshaler.unmarshal(buffer); @@ -1945,8 +1950,12 @@ public class CameraMetadataNative implements Parcelable { setBase(key.getNativeKey(), value); } - private <T> void setBase(Key<T> key, T value) { - int tag; + // The whole method needs to be synchronized since we're making + // multiple calls to the native layer. From one call to the other (within setBase) + // we expect the metadata's properties such as vendor id etc to + // stay the same and as a result the whole method should be synchronized for safety. + private synchronized <T> void setBase(Key<T> key, T value) { + int tag, nativeType; if (key.hasTag()) { tag = key.getTag(); } else { @@ -1959,7 +1968,7 @@ public class CameraMetadataNative implements Parcelable { return; } // else update the entry to a new value - int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); + nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag); Marshaler<T> marshaler = getMarshalerForKey(key, nativeType); int size = marshaler.calculateMarshalSize(value); @@ -2162,7 +2171,7 @@ public class CameraMetadataNative implements Parcelable { return true; } - private void updateNativeAllocation() { + private synchronized void updateNativeAllocation() { long currentBufferSize = nativeGetBufferSize(mMetadataPtr); if (currentBufferSize != mBufferSize) { @@ -2245,6 +2254,11 @@ public class CameraMetadataNative implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private long mMetadataPtr; // native std::shared_ptr<CameraMetadata>* + // FastNative doesn't work with synchronized methods and we can do synchronization + // wherever needed in the java layer (caller). At some places in java such as + // setBase() / getBase(), we do need to synchronize the whole method, so leaving + // synchronized out for these native methods. + @FastNative private static native long nativeAllocate(); @FastNative @@ -2254,28 +2268,41 @@ public class CameraMetadataNative implements Parcelable { @FastNative private static native void nativeUpdate(long dst, long src); - private static synchronized native void nativeWriteToParcel(Parcel dest, long ptr); - private static synchronized native void nativeReadFromParcel(Parcel source, long ptr); - private static synchronized native void nativeSwap(long ptr, long otherPtr) + @FastNative + private static native void nativeWriteToParcel(Parcel dest, long ptr); + @FastNative + private static native void nativeReadFromParcel(Parcel source, long ptr); + @FastNative + private static native void nativeSwap(long ptr, long otherPtr) throws NullPointerException; @FastNative private static native void nativeSetVendorId(long ptr, long vendorId); - private static synchronized native void nativeClose(long ptr); - private static synchronized native boolean nativeIsEmpty(long ptr); - private static synchronized native int nativeGetEntryCount(long ptr); - private static synchronized native long nativeGetBufferSize(long ptr); + @FastNative + private static native void nativeClose(long ptr); + @FastNative + private static native boolean nativeIsEmpty(long ptr); + @FastNative + private static native int nativeGetEntryCount(long ptr); + @FastNative + private static native long nativeGetBufferSize(long ptr); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static synchronized native byte[] nativeReadValues(int tag, long ptr); - private static synchronized native void nativeWriteValues(int tag, byte[] src, long ptr); - private static synchronized native void nativeDump(long ptr) throws IOException; // dump to LOGD + @FastNative + private static native byte[] nativeReadValues(int tag, long ptr); + @FastNative + private static native void nativeWriteValues(int tag, byte[] src, long ptr); + @FastNative + private static native void nativeDump(long ptr) throws IOException; // dump to LOGD - private static synchronized native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass); + @FastNative + private static native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass); @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static synchronized native int nativeGetTagFromKeyLocal(long ptr, String keyName) + @FastNative + private static native int nativeGetTagFromKeyLocal(long ptr, String keyName) throws IllegalArgumentException; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - private static synchronized native int nativeGetTypeFromTagLocal(long ptr, int tag) + @FastNative + private static native int nativeGetTypeFromTagLocal(long ptr, int tag) throws IllegalArgumentException; @FastNative private static native int nativeGetTagFromKey(String keyName, long vendorId) @@ -2293,7 +2320,7 @@ public class CameraMetadataNative implements Parcelable { * @throws NullPointerException if other was null * @hide */ - public void swap(CameraMetadataNative other) { + public synchronized void swap(CameraMetadataNative other) { nativeSwap(mMetadataPtr, other.mMetadataPtr); mCameraId = other.mCameraId; mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams; @@ -2308,14 +2335,14 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public void setVendorId(long vendorId) { + public synchronized void setVendorId(long vendorId) { nativeSetVendorId(mMetadataPtr, vendorId); } /** * @hide */ - public int getEntryCount() { + public synchronized int getEntryCount() { return nativeGetEntryCount(mMetadataPtr); } @@ -2324,7 +2351,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public boolean isEmpty() { + public synchronized boolean isEmpty() { return nativeIsEmpty(mMetadataPtr); } @@ -2343,7 +2370,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) { + public synchronized <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) { if (keyClass == null) { throw new NullPointerException(); } @@ -2398,7 +2425,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public void writeValues(int tag, byte[] src) { + public synchronized void writeValues(int tag, byte[] src) { nativeWriteValues(tag, src, mMetadataPtr); } @@ -2413,7 +2440,7 @@ public class CameraMetadataNative implements Parcelable { * @return {@code null} if there were 0 entries for this tag, a byte[] otherwise. * @hide */ - public byte[] readValues(int tag) { + public synchronized byte[] readValues(int tag) { // TODO: Optimization. Native code returns a ByteBuffer instead. return nativeReadValues(tag, mMetadataPtr); } @@ -2426,7 +2453,7 @@ public class CameraMetadataNative implements Parcelable { * * @hide */ - public void dumpToLog() { + public synchronized void dumpToLog() { try { nativeDump(mMetadataPtr); } catch (IOException e) { diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index a452226c81ac..28da644dd837 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -16,6 +16,7 @@ package android.hardware.display; +import static android.Manifest.permission.MANAGE_DISPLAYS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.HdrCapabilities.HdrType; import static android.view.Display.INVALID_DISPLAY; @@ -1764,6 +1765,29 @@ public final class DisplayManager { } /** + * @return The current display topology that represents the relative positions of extended + * displays. + * + * @hide + */ + @RequiresPermission(MANAGE_DISPLAYS) + @Nullable + public DisplayTopology getDisplayTopology() { + return mGlobal.getDisplayTopology(); + } + + /** + * Set the relative positions between extended displays (display topology). + * @param topology The display topology to be set + * + * @hide + */ + @RequiresPermission(MANAGE_DISPLAYS) + public void setDisplayTopology(DisplayTopology topology) { + mGlobal.setDisplayTopology(topology); + } + + /** * Listens for changes in available display devices. */ public interface DisplayListener { diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 644850a5c2e1..03b44f63e3b7 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -18,6 +18,7 @@ package android.hardware.display; import static android.hardware.display.DisplayManager.EventFlag; +import static android.Manifest.permission.MANAGE_DISPLAYS; import static android.view.Display.HdrCapabilities.HdrType; import android.Manifest; @@ -1285,6 +1286,31 @@ public final class DisplayManagerGlobal { } } + /** + * @see DisplayManager#getDisplayTopology + */ + @RequiresPermission(MANAGE_DISPLAYS) + @Nullable + public DisplayTopology getDisplayTopology() { + try { + return mDm.getDisplayTopology(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * @see DisplayManager#setDisplayTopology + */ + @RequiresPermission(MANAGE_DISPLAYS) + public void setDisplayTopology(DisplayTopology topology) { + try { + mDm.setDisplayTopology(topology); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, @DisplayEvent int event) { diff --git a/core/java/android/hardware/display/DisplayTopology.aidl b/core/java/android/hardware/display/DisplayTopology.aidl new file mode 100644 index 000000000000..e69b777a30de --- /dev/null +++ b/core/java/android/hardware/display/DisplayTopology.aidl @@ -0,0 +1,19 @@ +/* + * 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.display; + +parcelable DisplayTopology; diff --git a/services/core/java/com/android/server/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index fdadafeb98c9..e349b81614bc 100644 --- a/services/core/java/com/android/server/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -14,25 +14,34 @@ * limitations under the License. */ -package com.android.server.display; +package android.hardware.display; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_LEFT; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP; -import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT; +import static android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP; +import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; import android.util.IndentingPrintWriter; import android.util.Pair; import android.util.Slog; import android.view.Display; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; @@ -42,24 +51,59 @@ import java.util.Queue; /** * Represents the relative placement of extended displays. * Does not support concurrent calls, so a lock should be held when calling into this class. + * + * @hide */ -class DisplayTopology { +public final class DisplayTopology implements Parcelable { private static final String TAG = "DisplayTopology"; private static final float EPSILON = 0.0001f; + @android.annotation.NonNull + public static final Creator<DisplayTopology> CREATOR = + new Creator<>() { + @Override + public DisplayTopology createFromParcel(Parcel source) { + return new DisplayTopology(source); + } + + @Override + public DisplayTopology[] newArray(int size) { + return new DisplayTopology[size]; + } + }; + /** * The topology tree */ @Nullable - @VisibleForTesting - TreeNode mRoot; + private TreeNode mRoot; /** * The logical display ID of the primary display that will show certain UI elements. * This is not necessarily the same as the default display. */ + private int mPrimaryDisplayId = Display.INVALID_DISPLAY; + + public DisplayTopology() {} + @VisibleForTesting - int mPrimaryDisplayId = Display.INVALID_DISPLAY; + public DisplayTopology(TreeNode root, int primaryDisplayId) { + mRoot = root; + mPrimaryDisplayId = primaryDisplayId; + } + + public DisplayTopology(Parcel source) { + this(source.readTypedObject(TreeNode.CREATOR), source.readInt()); + } + + @Nullable + public TreeNode getRoot() { + return mRoot; + } + + public int getPrimaryDisplayId() { + return mPrimaryDisplayId; + } /** * Add a display to the topology. @@ -69,7 +113,7 @@ class DisplayTopology { * @param width The width of the display * @param height The height of the display */ - void addDisplay(int displayId, float width, float height) { + public void addDisplay(int displayId, float width, float height) { addDisplay(displayId, width, height, /* shouldLog= */ true); } @@ -79,7 +123,7 @@ class DisplayTopology { * one by one. * @param displayId The logical display ID */ - void removeDisplay(int displayId) { + public void removeDisplay(int displayId) { if (findDisplay(displayId, mRoot) == null) { return; } @@ -106,11 +150,22 @@ class DisplayTopology { } } + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mRoot, flags); + dest.writeInt(mPrimaryDisplayId); + } + /** * Print the object's state and debug information into the given stream. * @param pw The stream to dump information to. */ - void dump(PrintWriter pw) { + public void dump(PrintWriter pw) { pw.println("DisplayTopology:"); pw.println("--------------------"); IndentingPrintWriter ipw = new IndentingPrintWriter(pw); @@ -126,13 +181,21 @@ class DisplayTopology { } } + @Override + public String toString() { + StringWriter out = new StringWriter(); + PrintWriter writer = new PrintWriter(out); + dump(writer); + return out.toString(); + } + private void addDisplay(int displayId, float width, float height, boolean shouldLog) { if (findDisplay(displayId, mRoot) != null) { throw new IllegalArgumentException( "DisplayTopology: attempting to add a display that already exists"); } if (mRoot == null) { - mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0); + mRoot = new TreeNode(displayId, width, height, /* position= */ 0, /* offset= */ 0); mPrimaryDisplayId = displayId; if (shouldLog) { Slog.i(TAG, "First display added: " + mRoot); @@ -241,7 +304,7 @@ class DisplayTopology { * Update the topology to remove any overlaps between displays. */ @VisibleForTesting - void normalize() { + public void normalize() { if (mRoot == null) { return; } @@ -341,6 +404,8 @@ class DisplayTopology { case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left); case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom); case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top); + default -> throw new IllegalStateException( + "Unexpected value: " + targetDisplay.mPosition); }; // Check that the offset is within bounds areTouching &= switch (targetDisplay.mPosition) { @@ -350,6 +415,8 @@ class DisplayTopology { case POSITION_TOP, POSITION_BOTTOM -> childBounds.right + EPSILON >= parentBounds.left && childBounds.left <= parentBounds.right + EPSILON; + default -> throw new IllegalStateException( + "Unexpected value: " + targetDisplay.mPosition); }; if (!areTouching) { @@ -379,36 +446,56 @@ class DisplayTopology { * @param b second float to compare * @return whether the two values are within a small enough tolerance value */ - public static boolean floatEquals(float a, float b) { - return a == b || Float.isNaN(a) && Float.isNaN(b) || Math.abs(a - b) < EPSILON; + private static boolean floatEquals(float a, float b) { + return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON; } - @VisibleForTesting - static class TreeNode { + public static final class TreeNode implements Parcelable { + public static final int POSITION_LEFT = 0; + public static final int POSITION_TOP = 1; + public static final int POSITION_RIGHT = 2; + public static final int POSITION_BOTTOM = 3; + + @IntDef(prefix = { "POSITION_" }, value = { + POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Position{} + + @android.annotation.NonNull + public static final Creator<TreeNode> CREATOR = + new Creator<>() { + @Override + public TreeNode createFromParcel(Parcel source) { + return new TreeNode(source); + } + + @Override + public TreeNode[] newArray(int size) { + return new TreeNode[size]; + } + }; /** * The logical display ID */ - @VisibleForTesting - final int mDisplayId; + private final int mDisplayId; /** * The width of the display in density-independent pixels (dp). */ - @VisibleForTesting - float mWidth; + private final float mWidth; /** * The height of the display in density-independent pixels (dp). */ - @VisibleForTesting - float mHeight; + private final float mHeight; /** * The position of this display relative to its parent. */ - @VisibleForTesting - Position mPosition; + @Position + private int mPosition; /** * The distance from the top edge of the parent display to the top edge of this display (in @@ -416,13 +503,13 @@ class DisplayTopology { * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit * used is density-independent pixels (dp). */ - @VisibleForTesting - float mOffset; + private float mOffset; - @VisibleForTesting - final List<TreeNode> mChildren = new ArrayList<>(); + private final List<TreeNode> mChildren = new ArrayList<>(); - TreeNode(int displayId, float width, float height, Position position, float offset) { + @VisibleForTesting + public TreeNode(int displayId, float width, float height, @Position int position, + float offset) { mDisplayId = displayId; mWidth = width; mHeight = height; @@ -430,11 +517,76 @@ class DisplayTopology { mOffset = offset; } + public TreeNode(Parcel source) { + this(source.readInt(), source.readFloat(), source.readFloat(), source.readInt(), + source.readFloat()); + source.readTypedList(mChildren, CREATOR); + } + + public int getDisplayId() { + return mDisplayId; + } + + public float getWidth() { + return mWidth; + } + + public float getHeight() { + return mHeight; + } + + public int getPosition() { + return mPosition; + } + + public float getOffset() { + return mOffset; + } + + public List<TreeNode> getChildren() { + return Collections.unmodifiableList(mChildren); + } + + @Override + public String toString() { + return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight + + ", position=" + positionToString(mPosition) + ", offset=" + mOffset + "}"; + } + + /** + * @param position The position + * @return The string representation + */ + public static String positionToString(@Position int position) { + return switch (position) { + case POSITION_LEFT -> "left"; + case POSITION_TOP -> "top"; + case POSITION_RIGHT -> "right"; + case POSITION_BOTTOM -> "bottom"; + default -> throw new IllegalStateException("Unexpected value: " + position); + }; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mDisplayId); + dest.writeFloat(mWidth); + dest.writeFloat(mHeight); + dest.writeInt(mPosition); + dest.writeFloat(mOffset); + dest.writeTypedList(mChildren); + } + /** * Print the object's state and debug information into the given stream. * @param ipw The stream to dump information to. */ - void dump(IndentingPrintWriter ipw) { + public void dump(IndentingPrintWriter ipw) { ipw.println(this); ipw.increaseIndent(); for (TreeNode child : mChildren) { @@ -443,15 +595,12 @@ class DisplayTopology { ipw.decreaseIndent(); } - @Override - public String toString() { - return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight - + ", position=" + mPosition + ", offset=" + mOffset + "}"; - } - + /** + * @param child The child to add + */ @VisibleForTesting - enum Position { - POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM + public void addChild(TreeNode child) { + mChildren.add(child); } } } diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index b612bca5671e..4fbdf7f5afc8 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -23,6 +23,7 @@ import android.hardware.display.BrightnessConfiguration; import android.hardware.display.BrightnessInfo; import android.hardware.display.Curve; import android.hardware.graphics.common.DisplayDecorationSupport; +import android.hardware.display.DisplayTopology; import android.hardware.display.HdrConversionMode; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; @@ -254,4 +255,13 @@ interface IDisplayManager { // Get the default doze brightness @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS") float getDefaultDozeBrightness(int displayId); + + // Get the display topology + @EnforcePermission("MANAGE_DISPLAYS") + @nullable + DisplayTopology getDisplayTopology(); + + // Set the display topology + @EnforcePermission("MANAGE_DISPLAYS") + void setDisplayTopology(in DisplayTopology topology); } diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java index 858ec23ebed8..af715e485b73 100644 --- a/core/java/android/hardware/location/ContextHubInfo.java +++ b/core/java/android/hardware/location/ContextHubInfo.java @@ -15,7 +15,6 @@ */ package android.hardware.location; -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 6284e7061b88..494bfc926384 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -18,6 +18,7 @@ package android.hardware.location; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -31,7 +32,6 @@ import android.app.ActivityThread; import android.app.PendingIntent; import android.chre.flags.Flags; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.contexthub.ErrorCode; import android.os.Handler; @@ -484,15 +484,33 @@ public final class ContextHubManager { } } - /** - * Helper function to generate a stub for a query transaction callback. - * - * @param transaction the transaction to unblock when complete - * - * @return the callback - * - * @hide - */ + /** + * Returns the list of HubInfo objects describing the available hubs (including ContextHub and + * VendorHub). This method is primarily used for debugging purposes as most clients care about + * endpoints and services more than hubs. + * + * @return the list of HubInfo objects + * @see HubInfo + * @hide + */ + @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @NonNull + @FlaggedApi(Flags.FLAG_OFFLOAD_API) + public List<HubInfo> getHubs() { + try { + return mService.getHubs(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Helper function to generate a stub for a query transaction callback. + * + * @param transaction the transaction to unblock when complete + * @return the callback + * @hide + */ private IContextHubTransactionCallback createQueryCallback( ContextHubTransaction<List<NanoAppState>> transaction) { return new IContextHubTransactionCallback.Stub() { diff --git a/core/java/android/hardware/location/HubInfo.aidl b/core/java/android/hardware/location/HubInfo.aidl new file mode 100644 index 000000000000..25b5b0aa1222 --- /dev/null +++ b/core/java/android/hardware/location/HubInfo.aidl @@ -0,0 +1,20 @@ +/* + * 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.location; + +/** @hide */ +parcelable HubInfo; diff --git a/core/java/android/hardware/location/HubInfo.java b/core/java/android/hardware/location/HubInfo.java new file mode 100644 index 000000000000..f7de1279672c --- /dev/null +++ b/core/java/android/hardware/location/HubInfo.java @@ -0,0 +1,153 @@ +/* + * 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.location; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.chre.flags.Flags; +import android.os.BadParcelableException; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Union type for {@link ContextHubInfo} and {@link VendorHubInfo} + * + * @hide + */ +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class HubInfo implements Parcelable { + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {TYPE_CONTEXT_HUB, TYPE_VENDOR_HUB}) + private @interface HubType {} + + public static final int TYPE_CONTEXT_HUB = 0; + public static final int TYPE_VENDOR_HUB = 1; + + private final long mId; + @HubType private final int mType; + @Nullable private final ContextHubInfo mContextHubInfo; + @Nullable private final VendorHubInfo mVendorHubInfo; + + /** @hide */ + public HubInfo(long id, @NonNull ContextHubInfo contextHubInfo) { + mId = id; + mType = TYPE_CONTEXT_HUB; + mContextHubInfo = contextHubInfo; + mVendorHubInfo = null; + } + + /** @hide */ + public HubInfo(long id, @NonNull VendorHubInfo vendorHubInfo) { + mId = id; + mType = TYPE_VENDOR_HUB; + mContextHubInfo = null; + mVendorHubInfo = vendorHubInfo; + } + + private HubInfo(Parcel in) { + mId = in.readLong(); + mType = in.readInt(); + + switch (mType) { + case TYPE_CONTEXT_HUB: + mContextHubInfo = ContextHubInfo.CREATOR.createFromParcel(in); + mVendorHubInfo = null; + break; + case TYPE_VENDOR_HUB: + mVendorHubInfo = VendorHubInfo.CREATOR.createFromParcel(in); + mContextHubInfo = null; + break; + default: + throw new BadParcelableException("Parcelable has invalid type"); + } + } + + /** Get the hub unique identifier */ + public long getId() { + return mId; + } + + /** Get the hub type. The type can be {@link TYPE_CONTEXT_HUB} or {@link TYPE_VENDOR_HUB} */ + public int getType() { + return mType; + } + + /** Get the {@link ContextHubInfo} object, null if type is not {@link TYPE_CONTEXT_HUB} */ + @Nullable + public ContextHubInfo getContextHubInfo() { + return mContextHubInfo; + } + + /** Parcel implementation details */ + public int describeContents() { + if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) { + return mContextHubInfo.describeContents(); + } + if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) { + return mVendorHubInfo.describeContents(); + } + return 0; + } + + /** Parcel implementation details */ + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeLong(mId); + out.writeInt(mType); + + if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) { + mContextHubInfo.writeToParcel(out, flags); + } + + if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) { + mVendorHubInfo.writeToParcel(out, flags); + } + } + + @NonNull + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("HubInfo ID: 0x"); + out.append(Long.toHexString(mId)); + out.append("\n"); + if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) { + out.append(" ContextHubDetails: "); + out.append(mContextHubInfo); + } + if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) { + out.append(" VendorHubDetails: "); + out.append(mVendorHubInfo); + } + return out.toString(); + } + + public static final @NonNull Creator<HubInfo> CREATOR = + new Creator<>() { + public HubInfo createFromParcel(Parcel in) { + return new HubInfo(in); + } + + public HubInfo[] newArray(int size) { + return new HubInfo[size]; + } + }; +} diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index 11f3046150d3..b0cc763dc8fd 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -18,6 +18,7 @@ package android.hardware.location; // Declare any non-default types here with import statements import android.app.PendingIntent; +import android.hardware.location.HubInfo; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubMessage; import android.hardware.location.NanoApp; @@ -82,6 +83,10 @@ interface IContextHubService { @EnforcePermission("ACCESS_CONTEXT_HUB") List<ContextHubInfo> getContextHubs(); + // Returns a list of HubInfo objects of available hubs (including ContextHub and VendorHub) + @EnforcePermission("ACCESS_CONTEXT_HUB") + List<HubInfo> getHubs(); + // Loads a nanoapp at the specified hub (new API) @EnforcePermission("ACCESS_CONTEXT_HUB") void loadNanoAppOnHub( diff --git a/core/java/android/hardware/location/VendorHubInfo.aidl b/core/java/android/hardware/location/VendorHubInfo.aidl new file mode 100644 index 000000000000..a7936acbb654 --- /dev/null +++ b/core/java/android/hardware/location/VendorHubInfo.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 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.location; + +/** @hide */ +parcelable VendorHubInfo;
\ No newline at end of file diff --git a/core/java/android/hardware/location/VendorHubInfo.java b/core/java/android/hardware/location/VendorHubInfo.java new file mode 100644 index 000000000000..26772b18176f --- /dev/null +++ b/core/java/android/hardware/location/VendorHubInfo.java @@ -0,0 +1,95 @@ +/* + * 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.location; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.chre.flags.Flags; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelableHolder; + +/** + * Information about a VendorHub. VendorHub is similar to ContextHub, but it does not run the + * Context Hub Runtime Environment (or nano apps). It provides a unified endpoint messaging API + * through the ContextHub V4 HAL. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_OFFLOAD_API) +public final class VendorHubInfo implements Parcelable { + private final String mName; + private final int mVersion; + private final ParcelableHolder mExtendedInfo; + + /** @hide */ + public VendorHubInfo(android.hardware.contexthub.VendorHubInfo halHubInfo) { + mName = halHubInfo.name; + mVersion = halHubInfo.version; + mExtendedInfo = halHubInfo.extendedInfo; + } + + private VendorHubInfo(Parcel in) { + mName = in.readString(); + mVersion = in.readInt(); + mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in); + } + + /** Get the hub name */ + @NonNull + public String getName() { + return mName; + } + + /** Get the hub version */ + public int getVersion() { + return mVersion; + } + + /** Parcel implementation details */ + public int describeContents() { + return mExtendedInfo.describeContents(); + } + + /** Parcel implementation details */ + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeString(mName); + out.writeInt(mVersion); + mExtendedInfo.writeToParcel(out, flags); + } + + @NonNull + @Override + public String toString() { + StringBuilder out = new StringBuilder(); + out.append("VendorHub Name : "); + out.append(mName); + out.append(", Version : "); + out.append(mVersion); + return out.toString(); + } + + public static final @NonNull Creator<VendorHubInfo> CREATOR = + new Creator<>() { + public VendorHubInfo createFromParcel(Parcel in) { + return new VendorHubInfo(in); + } + + public VendorHubInfo[] newArray(int size) { + return new VendorHubInfo[size]; + } + }; +} diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index 72f2de805474..dfc11dcb5427 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -67,3 +67,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "cleanup_dream_settings_on_uninstall" + namespace: "systemui" + description: "Cleans up dream settings if dream package is uninstalled." + bug: "338210427" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/service/settings/preferences/GetValueRequest.aidl b/core/java/android/service/settings/preferences/GetValueRequest.aidl new file mode 100644 index 000000000000..2a0eb09aa2a4 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueRequest.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable GetValueRequest;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/GetValueRequest.java b/core/java/android/service/settings/preferences/GetValueRequest.java new file mode 100644 index 000000000000..4f82800d1855 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueRequest.java @@ -0,0 +1,139 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.util.Objects; + +/** + * Request parameters to retrieve the current value of a Settings Preference. + * + * <p>This object passed to {@link SettingsPreferenceService#onGetPreferenceValue} will result + * in a {@link GetValueResult}. + * + * <ul> + * <li>{@link #getScreenKey} is a parameter to distinguish the container screen + * of a preference as a preference key may not be unique within its application. + * <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is + * being requested. These keys will be unique with their Preference Screen, but may not be unique + * within their application, so it is required to pair this with {@link #getScreenKey} to + * ensure this request matches the intended target. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class GetValueRequest implements Parcelable { + + @NonNull + private final String mScreenKey; + @NonNull + private final String mPreferenceKey; + + /** + * Returns the screen key of requested Preference. + */ + @NonNull + public String getScreenKey() { + return mScreenKey; + } + + /** + * Returns the key of requested Preference. + */ + @NonNull + public String getPreferenceKey() { + return mPreferenceKey; + } + + private GetValueRequest(@NonNull Builder builder) { + mScreenKey = builder.mScreenKey; + mPreferenceKey = builder.mPreferenceKey; + } + + private GetValueRequest(@NonNull Parcel in) { + mScreenKey = Objects.requireNonNull(in.readString8()); + mPreferenceKey = Objects.requireNonNull(in.readString8()); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mScreenKey); + dest.writeString8(mPreferenceKey); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link GetValueRequest}. + */ + @NonNull + public static final Creator<GetValueRequest> CREATOR = new Creator<GetValueRequest>() { + @Override + public GetValueRequest createFromParcel(@NonNull Parcel in) { + return new GetValueRequest(in); + } + + @Override + public GetValueRequest[] newArray(int size) { + return new GetValueRequest[size]; + } + }; + + /** + * Builder to construct {@link GetValueRequest}. + */ + public static final class Builder { + private final String mScreenKey; + private final String mPreferenceKey; + + /** + * Create Builder instance. + * @param screenKey required to be not empty + * @param preferenceKey required to be not empty + */ + public Builder(@NonNull String screenKey, @NonNull String preferenceKey) { + if (TextUtils.isEmpty(screenKey)) { + throw new IllegalArgumentException("screenKey cannot be empty"); + } + if (TextUtils.isEmpty(preferenceKey)) { + throw new IllegalArgumentException("preferenceKey cannot be empty"); + } + mScreenKey = screenKey; + mPreferenceKey = preferenceKey; + } + + /** + * Constructs an immutable {@link GetValueRequest} object. + */ + @NonNull + public GetValueRequest build() { + return new GetValueRequest(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/GetValueResult.aidl b/core/java/android/service/settings/preferences/GetValueResult.aidl new file mode 100644 index 000000000000..b5ebd35a3a37 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueResult.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable GetValueResult;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/GetValueResult.java b/core/java/android/service/settings/preferences/GetValueResult.java new file mode 100644 index 000000000000..369dea77cc85 --- /dev/null +++ b/core/java/android/service/settings/preferences/GetValueResult.java @@ -0,0 +1,213 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result object given a corresponding {@link GetValueRequest}. + * <ul> + * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK}, + * {@link #getValue} will be populated with the settings preference value and + * {@link #getMetadata} will be populated with its metadata. + * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than + * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause + * of the failure. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class GetValueResult implements Parcelable { + + @ResultCode + private final int mResultCode; + @Nullable + private final SettingsPreferenceValue mValue; + @Nullable + private final SettingsPreferenceMetadata mMetadata; + + /** + * Returns the result code indicating status of the request. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Returns the value of requested Preference if request successful. + */ + @Nullable + public SettingsPreferenceValue getValue() { + return mValue; + } + + /** + * Returns the metadata of requested Preference if request successful. + */ + @Nullable + public SettingsPreferenceMetadata getMetadata() { + return mMetadata; + } + + /** @hide */ + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_OK, + RESULT_UNSUPPORTED, + RESULT_UNAVAILABLE, + RESULT_REQUIRE_APP_PERMISSION, + RESULT_DISALLOW, + RESULT_INVALID_REQUEST, + RESULT_INTERNAL_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode { + } + + /** Request is successful. */ + public static final int RESULT_OK = 0; + /** + * Requested preference is not supported by this API. + * <p>Retry not advised. + */ + public static final int RESULT_UNSUPPORTED = 1; + /** + * Preference is currently not available, likely due to device state or the state of + * a dependency. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_UNAVAILABLE = 2; + /** + * Requested preference requires permissions not held by the calling application. + * <p>Retry may succeed if necessary permissions are obtained. + */ + public static final int RESULT_REQUIRE_APP_PERMISSION = 3; + /** + * Requested preference is not allowed for access in this API under the current device policy. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_DISALLOW = 4; + /** + * Request object is not valid. + * <p>Retry not advised with current parameters. + */ + public static final int RESULT_INVALID_REQUEST = 5; + /** + * API call failed due to an issue with the service binding. + * <p>Retry may succeed. + */ + public static final int RESULT_INTERNAL_ERROR = 6; + + + private GetValueResult(@NonNull Builder builder) { + mResultCode = builder.mResultCode; + mValue = builder.mValue; + mMetadata = builder.mMetadata; + } + + private GetValueResult(@NonNull Parcel in) { + mResultCode = in.readInt(); + mValue = in.readParcelable(SettingsPreferenceValue.class.getClassLoader(), + SettingsPreferenceValue.class); + mMetadata = in.readParcelable(SettingsPreferenceMetadata.class.getClassLoader(), + SettingsPreferenceMetadata.class); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mResultCode); + dest.writeParcelable(mValue, flags); + dest.writeParcelable(mMetadata, flags); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link GetValueResult}. + */ + @NonNull + public static final Creator<GetValueResult> CREATOR = new Creator<>() { + @Override + public GetValueResult createFromParcel(@NonNull Parcel in) { + return new GetValueResult(in); + } + + @Override + public GetValueResult[] newArray(int size) { + return new GetValueResult[size]; + } + }; + + /** + * Builder to construct {@link GetValueResult}. + */ + public static final class Builder { + @ResultCode + private final int mResultCode; + private SettingsPreferenceValue mValue; + private SettingsPreferenceMetadata mMetadata; + + /** + * Create Builder instance. + * @param resultCode indicates status of the request + */ + public Builder(@ResultCode int resultCode) { + mResultCode = resultCode; + } + + /** + * Sets the preference value on the result. + */ + @NonNull + public Builder setValue(@Nullable SettingsPreferenceValue value) { + mValue = value; + return this; + } + + /** + * Sets the metadata on the result. + */ + @NonNull + public Builder setMetadata(@Nullable SettingsPreferenceMetadata metadata) { + mMetadata = metadata; + return this; + } + + /** + * Constructs an immutable {@link GetValueResult} object. + */ + @NonNull + public GetValueResult build() { + return new GetValueResult(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/IGetValueCallback.aidl b/core/java/android/service/settings/preferences/IGetValueCallback.aidl new file mode 100644 index 000000000000..bbc7423f453e --- /dev/null +++ b/core/java/android/service/settings/preferences/IGetValueCallback.aidl @@ -0,0 +1,9 @@ +package android.service.settings.preferences; + +import android.service.settings.preferences.GetValueResult; + +/** @hide */ +oneway interface IGetValueCallback { + void onSuccess(in GetValueResult result) = 1; + void onFailure() = 2; +} diff --git a/core/java/android/service/settings/preferences/IMetadataCallback.aidl b/core/java/android/service/settings/preferences/IMetadataCallback.aidl new file mode 100644 index 000000000000..3bd5ebe93660 --- /dev/null +++ b/core/java/android/service/settings/preferences/IMetadataCallback.aidl @@ -0,0 +1,9 @@ +package android.service.settings.preferences; + +import android.service.settings.preferences.MetadataResult; + +/** @hide */ +oneway interface IMetadataCallback { + void onSuccess(in MetadataResult result); + void onFailure(); +} diff --git a/core/java/android/service/settings/preferences/ISetValueCallback.aidl b/core/java/android/service/settings/preferences/ISetValueCallback.aidl new file mode 100644 index 000000000000..0765660c83c3 --- /dev/null +++ b/core/java/android/service/settings/preferences/ISetValueCallback.aidl @@ -0,0 +1,9 @@ +package android.service.settings.preferences; + +import android.service.settings.preferences.SetValueResult; + +/** @hide */ +oneway interface ISetValueCallback { + void onSuccess(in SetValueResult result); + void onFailure(); +} diff --git a/core/java/android/service/settings/preferences/MetadataRequest.aidl b/core/java/android/service/settings/preferences/MetadataRequest.aidl new file mode 100644 index 000000000000..dc3cbc42661e --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataRequest.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable MetadataRequest;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/MetadataRequest.java b/core/java/android/service/settings/preferences/MetadataRequest.java new file mode 100644 index 000000000000..ffecc6bec5b2 --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataRequest.java @@ -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 android.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +/** + * Request parameters to retrieve all metadata for all available settings preferences within this + * application. + * + * <p>This object passed to {@link SettingsPreferenceService#onGetAllPreferenceMetadata} will result + * in a {@link MetadataResult}. + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class MetadataRequest implements Parcelable { + private MetadataRequest() {} + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link MetadataRequest}. + */ + @NonNull + public static final Creator<MetadataRequest> CREATOR = new Creator<>() { + @Override + public MetadataRequest createFromParcel(@NonNull Parcel in) { + return new MetadataRequest(); + } + + @Override + public MetadataRequest[] newArray(int size) { + return new MetadataRequest[size]; + } + }; + + /** + * Builder to construct {@link MetadataRequest}. + */ + public static final class Builder { + /** Constructs an immutable {@link MetadataRequest} object. */ + @NonNull + public MetadataRequest build() { + return new MetadataRequest(); + } + } +} diff --git a/core/java/android/service/settings/preferences/MetadataResult.aidl b/core/java/android/service/settings/preferences/MetadataResult.aidl new file mode 100644 index 000000000000..af9e8a86e3ab --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataResult.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable MetadataResult;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/MetadataResult.java b/core/java/android/service/settings/preferences/MetadataResult.java new file mode 100644 index 000000000000..6a65dcc9c757 --- /dev/null +++ b/core/java/android/service/settings/preferences/MetadataResult.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Result object given a corresponding {@link MetadataRequest}. + * <ul> + * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK} and + * {@link #getMetadataList} will be populated with metadata for all available preferences within + * this application. + * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than + * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause + * of the failure. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class MetadataResult implements Parcelable { + + @ResultCode + private final int mResultCode; + @NonNull + private final List<SettingsPreferenceMetadata> mMetadataList; + + /** + * Returns the result code indicating status of the request. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Returns the list of available Preference Metadata. + * <p>This instance is shared so this list should not be modified. + */ + @NonNull + public List<SettingsPreferenceMetadata> getMetadataList() { + return mMetadataList; + } + + /** @hide */ + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_OK, + RESULT_UNSUPPORTED, + RESULT_INTERNAL_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode { + } + + /** Request is successful. */ + public static final int RESULT_OK = 0; + /** + * No preferences in this application support this API. + * <p>Retry not advised. + */ + public static final int RESULT_UNSUPPORTED = 1; + /** + * API call failed due to an issue with the service binding. + * <p>Retry may succeed. + */ + public static final int RESULT_INTERNAL_ERROR = 2; + + private MetadataResult(@NonNull Builder builder) { + mResultCode = builder.mResultCode; + mMetadataList = builder.mMetadataList; + } + private MetadataResult(@NonNull Parcel in) { + mResultCode = in.readInt(); + mMetadataList = new ArrayList<>(); + in.readTypedList(mMetadataList, SettingsPreferenceMetadata.CREATOR); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mResultCode); + dest.writeTypedList(mMetadataList, flags); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link MetadataResult}. + */ + @NonNull + public static final Creator<MetadataResult> CREATOR = new Creator<>() { + @Override + public MetadataResult createFromParcel(@NonNull Parcel in) { + return new MetadataResult(in); + } + + @Override + public MetadataResult[] newArray(int size) { + return new MetadataResult[size]; + } + }; + + /** + * Builder to construct {@link MetadataResult}. + */ + public static final class Builder { + @ResultCode + private final int mResultCode; + private List<SettingsPreferenceMetadata> mMetadataList = Collections.emptyList(); + + /** + * Create Builder instance. + * @param resultCode indicates status of the request + */ + public Builder(@ResultCode int resultCode) { + mResultCode = resultCode; + } + + /** + * Sets the metadata list on the result. + */ + @NonNull + public Builder setMetadataList(@NonNull List<SettingsPreferenceMetadata> metadataList) { + mMetadataList = metadataList; + return this; + } + + /** + * Constructs an immutable {@link MetadataResult} object. + */ + @NonNull + public MetadataResult build() { + return new MetadataResult(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SetValueRequest.aidl b/core/java/android/service/settings/preferences/SetValueRequest.aidl new file mode 100644 index 000000000000..198e333d5cb6 --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueRequest.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable SetValueRequest;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/SetValueRequest.java b/core/java/android/service/settings/preferences/SetValueRequest.java new file mode 100644 index 000000000000..f7600aecdfaf --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueRequest.java @@ -0,0 +1,158 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.util.Objects; + +/** + * Request parameters to set the current value to a Settings Preference. + * <p>This object passed to {@link SettingsPreferenceService#onSetPreferenceValue} will result in a + * {@link SetValueResult}. + * <ul> + * <li>{@link #getScreenKey} is a parameter to distinguish the container screen + * of a preference as a preference key may not be unique within its application. + * <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is + * being requested. These keys will be unique with their Preference Screen, but may not be unique + * within their application, so it is required to pair this with {@link #getScreenKey} to + * ensure this request matches the intended target. + * <li>{@link #getPreferenceValue} is a parameter to specify the value that this request aims to + * set. If this value is invalid (malformed or does not match the type of the preference) then + * this request will fail. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SetValueRequest implements Parcelable { + + @NonNull + private final String mScreenKey; + @NonNull + private final String mPreferenceKey; + @NonNull + private final SettingsPreferenceValue mPreferenceValue; + + /** + * Returns the screen key of requested Preference. + */ + @NonNull + public String getScreenKey() { + return mScreenKey; + } + + /** + * Returns the key of requested Preference. + */ + @NonNull + public String getPreferenceKey() { + return mPreferenceKey; + } + + /** + * Returns the value of requested Preference. + */ + @NonNull + public SettingsPreferenceValue getPreferenceValue() { + return mPreferenceValue; + } + + private SetValueRequest(@NonNull Builder builder) { + mScreenKey = builder.mScreenKey; + mPreferenceKey = builder.mPreferenceKey; + mPreferenceValue = builder.mPreferenceValue; + } + + private SetValueRequest(@NonNull Parcel in) { + mScreenKey = Objects.requireNonNull(in.readString8()); + mPreferenceKey = Objects.requireNonNull(in.readString8()); + mPreferenceValue = Objects.requireNonNull(in.readParcelable( + SettingsPreferenceValue.class.getClassLoader(), SettingsPreferenceValue.class)); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mScreenKey); + dest.writeString8(mPreferenceKey); + dest.writeParcelable(mPreferenceValue, flags); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link SetValueRequest}. + */ + @NonNull + public static final Creator<SetValueRequest> CREATOR = new Creator<SetValueRequest>() { + @Override + public SetValueRequest createFromParcel(@NonNull Parcel in) { + return new SetValueRequest(in); + } + + @Override + public SetValueRequest[] newArray(int size) { + return new SetValueRequest[size]; + } + }; + + /** + * Builder to construct {@link SetValueRequest}. + */ + public static final class Builder { + private final String mScreenKey; + private final String mPreferenceKey; + private final SettingsPreferenceValue mPreferenceValue; + + /** + * Create Builder instance. + * @param screenKey required to be not empty + * @param preferenceKey required to be not empty + * @param value value to set to requested Preference + */ + public Builder(@NonNull String screenKey, @NonNull String preferenceKey, + @NonNull SettingsPreferenceValue value) { + if (TextUtils.isEmpty(screenKey)) { + throw new IllegalArgumentException("screenKey cannot be empty"); + } + if (TextUtils.isEmpty(preferenceKey)) { + throw new IllegalArgumentException("preferenceKey cannot be empty"); + } + mScreenKey = screenKey; + mPreferenceKey = preferenceKey; + mPreferenceValue = value; + } + + /** + * Constructs an immutable {@link SetValueRequest} object. + */ + @NonNull + public SetValueRequest build() { + return new SetValueRequest(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SetValueResult.aidl b/core/java/android/service/settings/preferences/SetValueResult.aidl new file mode 100644 index 000000000000..f54813484d68 --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueResult.aidl @@ -0,0 +1,4 @@ +package android.service.settings.preferences; + +/** @hide */ +parcelable SetValueResult;
\ No newline at end of file diff --git a/core/java/android/service/settings/preferences/SetValueResult.java b/core/java/android/service/settings/preferences/SetValueResult.java new file mode 100644 index 000000000000..cb1776abd3bc --- /dev/null +++ b/core/java/android/service/settings/preferences/SetValueResult.java @@ -0,0 +1,179 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result object given a corresponding {@link SetValueRequest}. + * <ul> + * <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK}. + * <li>If the request is unsuccessful, {@link #getResultCode} be a value other than + * {@link #RESULT_OK} - see documentation for those possibilities to understand the cause + * of the failure. + * </ul> + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SetValueResult implements Parcelable { + + @ResultCode + private final int mResultCode; + + /** + * Returns the result code indicating status of the request. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** @hide */ + @IntDef(prefix = { "RESULT_" }, value = { + RESULT_OK, + RESULT_UNSUPPORTED, + RESULT_DISABLED, + RESULT_RESTRICTED, + RESULT_UNAVAILABLE, + RESULT_REQUIRE_APP_PERMISSION, + RESULT_REQUIRE_USER_CONSENT, + RESULT_DISALLOW, + RESULT_INVALID_REQUEST, + RESULT_INTERNAL_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode { + } + + /** Request is successful and the value was set. */ + public static final int RESULT_OK = 0; + /** + * Requested preference is not supported by this API. + * <p>Retry not advised. + */ + public static final int RESULT_UNSUPPORTED = 1; + /** + * Requested preference is disabled, thus unable to be set in this state. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_DISABLED = 2; + /** + * Requested preference is restricted, thus unable to be set under this policy. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_RESTRICTED = 3; + /** + * Preference is currently not available, likely due to device state or the state of + * a dependency. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_UNAVAILABLE = 4; + /** + * Requested preference requires permissions not held by the calling application. + * <p>Retry may succeed if necessary permissions are obtained. + */ + public static final int RESULT_REQUIRE_APP_PERMISSION = 5; + /** + * User consent was not approved for this operation. + * <p>Retry may succeed if user provides consent. + */ + public static final int RESULT_REQUIRE_USER_CONSENT = 6; + /** + * Requested preference is not allowed for access in this API under the current device policy. + * <p>Retry may succeed if underlying conditions change. + */ + public static final int RESULT_DISALLOW = 7; + /** + * Request object is not valid. + * <p>Retry not advised with current parameters. + */ + public static final int RESULT_INVALID_REQUEST = 8; + /** + * API call failed due to an issue with the service binding. + * <p>Retry may succeed. + */ + public static final int RESULT_INTERNAL_ERROR = 9; + + private SetValueResult(@NonNull Builder builder) { + mResultCode = builder.mResultCode; + } + + private SetValueResult(@NonNull Parcel in) { + mResultCode = in.readInt(); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mResultCode); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link SetValueResult}. + */ + @NonNull + public static final Creator<SetValueResult> CREATOR = new Creator<>() { + @Override + public SetValueResult createFromParcel(@NonNull Parcel in) { + return new SetValueResult(in); + } + + @Override + public SetValueResult[] newArray(int size) { + return new SetValueResult[size]; + } + }; + + /** + * Builder to construct {@link SetValueResult}. + */ + public static final class Builder { + @ResultCode + private final int mResultCode; + + /** + * Create Builder instance. + * @param resultCode indicates status of the request + */ + public Builder(@ResultCode int resultCode) { + mResultCode = resultCode; + } + + /** + * Constructs an immutable {@link SetValueResult} object. + */ + @NonNull + public SetValueResult build() { + return new SetValueResult(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java new file mode 100644 index 000000000000..1d08c5217129 --- /dev/null +++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java @@ -0,0 +1,436 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Data object representation of a Settings Preference definition and state. + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SettingsPreferenceMetadata implements Parcelable { + + @NonNull + private final String mKey; + @NonNull + private final String mScreenKey; + @Nullable + private final String mTitle; + @Nullable + private final String mSummary; + @NonNull + private final List<String> mBreadcrumbs; + @NonNull + private final List<String> mReadPermissions; + @NonNull + private final List<String> mWritePermissions; + private final boolean mEnabled; + private final boolean mAvailable; + private final boolean mWritable; + private final boolean mRestricted; + private final int mSensitivity; + @Nullable + private final PendingIntent mLaunchIntent; + @NonNull + private final Bundle mExtras; + + /** + * Returns the key of Preference. + */ + @NonNull + public String getKey() { + return mKey; + } + + /** + * Returns the screen key of Preference. + */ + @NonNull + public String getScreenKey() { + return mScreenKey; + } + + /** + * Returns the title of Preference. + */ + @Nullable + public String getTitle() { + return mTitle; + } + + /** + * Returns the summary of Preference. + */ + @Nullable + public String getSummary() { + return mSummary; + } + + /** + * Returns the breadcrumbs (navigation context) of Preference. + * <p>May be empty. + */ + @NonNull + public List<String> getBreadcrumbs() { + return mBreadcrumbs; + } + + /** + * Returns the permissions required to read this Preference's value. + * <p>May be empty. + */ + @NonNull + public List<String> getReadPermissions() { + return mReadPermissions; + } + + /** + * Returns the permissions required to write this Preference's value. + * <p>May be empty. + */ + @NonNull + public List<String> getWritePermissions() { + return mWritePermissions; + } + + /** + * Returns whether Preference is enabled. + */ + public boolean isEnabled() { + return mEnabled; + } + + /** + * Returns whether Preference is available. + */ + public boolean isAvailable() { + return mAvailable; + } + + /** + * Returns whether Preference is writable. + */ + public boolean isWritable() { + return mWritable; + } + + /** + * Returns whether Preference is restricted. + */ + public boolean isRestricted() { + return mRestricted; + } + + /** + * Returns the write-level sensitivity of Preference. + */ + @WriteSensitivity + public int getWriteSensitivity() { + return mSensitivity; + } + + /** + * Returns the intent to launch the host app page for this Preference. + */ + @Nullable + public PendingIntent getLaunchIntent() { + return mLaunchIntent; + } + + /** + * Returns any additional fields specific to this preference. + * <p>Treat all data as optional. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** @hide */ + @IntDef(value = { + NOT_SENSITIVE, + SENSITIVE, + INTENT_ONLY + }) + @Retention(RetentionPolicy.SOURCE) + public @interface WriteSensitivity {} + + /** + * Preference is not sensitive, thus its value is writable without explicit consent, assuming + * all necessary permissions are granted. + */ + public static final int NOT_SENSITIVE = 0; + /** + * Preference is sensitive, meaning that in addition to necessary permissions, writing its value + * will also request explicit user consent. + */ + public static final int SENSITIVE = 1; + /** + * Preference is not permitted for write-access via API and must be changed via Settings page. + */ + public static final int INTENT_ONLY = 2; + + private SettingsPreferenceMetadata(@NonNull Builder builder) { + mKey = builder.mKey; + mScreenKey = builder.mScreenKey; + mTitle = builder.mTitle; + mSummary = builder.mSummary; + mBreadcrumbs = builder.mBreadcrumbs; + mReadPermissions = builder.mReadPermissions; + mWritePermissions = builder.mWritePermissions; + mEnabled = builder.mEnabled; + mAvailable = builder.mAvailable; + mWritable = builder.mWritable; + mRestricted = builder.mRestricted; + mSensitivity = builder.mSensitivity; + mLaunchIntent = builder.mLaunchIntent; + mExtras = Objects.requireNonNullElseGet(builder.mExtras, Bundle::new); + } + @SuppressLint("ParcelClassLoader") + private SettingsPreferenceMetadata(@NonNull Parcel in) { + mKey = Objects.requireNonNull(in.readString8()); + mScreenKey = Objects.requireNonNull(in.readString8()); + mTitle = in.readString8(); + mSummary = in.readString8(); + mBreadcrumbs = new ArrayList<>(); + in.readStringList(mBreadcrumbs); + mReadPermissions = new ArrayList<>(); + in.readStringList(mReadPermissions); + mWritePermissions = new ArrayList<>(); + in.readStringList(mWritePermissions); + mEnabled = in.readBoolean(); + mAvailable = in.readBoolean(); + mWritable = in.readBoolean(); + mRestricted = in.readBoolean(); + mSensitivity = in.readInt(); + mLaunchIntent = in.readParcelable(PendingIntent.class.getClassLoader(), + PendingIntent.class); + mExtras = Objects.requireNonNullElseGet(in.readBundle(), Bundle::new); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mKey); + dest.writeString8(mScreenKey); + dest.writeString8(mTitle); + dest.writeString8(mSummary); + dest.writeStringList(mBreadcrumbs); + dest.writeStringList(mReadPermissions); + dest.writeStringList(mWritePermissions); + dest.writeBoolean(mEnabled); + dest.writeBoolean(mAvailable); + dest.writeBoolean(mWritable); + dest.writeBoolean(mRestricted); + dest.writeInt(mSensitivity); + dest.writeParcelable(mLaunchIntent, flags); + dest.writeBundle(mExtras); + } + + /** + * Parcelable Creator for {@link SettingsPreferenceMetadata}. + */ + @NonNull + public static final Creator<SettingsPreferenceMetadata> CREATOR = new Creator<>() { + @Override + public SettingsPreferenceMetadata createFromParcel(@NonNull Parcel in) { + return new SettingsPreferenceMetadata(in); + } + + @Override + public SettingsPreferenceMetadata[] newArray(int size) { + return new SettingsPreferenceMetadata[size]; + } + }; + + /** + * Builder to construct {@link SettingsPreferenceMetadata}. + */ + public static final class Builder { + private final String mScreenKey; + private final String mKey; + private String mTitle; + private String mSummary; + private List<String> mBreadcrumbs = Collections.emptyList(); + private List<String> mReadPermissions = Collections.emptyList(); + private List<String> mWritePermissions = Collections.emptyList(); + private boolean mEnabled = false; + private boolean mAvailable = false; + private boolean mWritable = false; + private boolean mRestricted = false; + @WriteSensitivity private int mSensitivity = INTENT_ONLY; + private PendingIntent mLaunchIntent; + private Bundle mExtras; + + /** + * Create Builder instance. + * @param screenKey required to be not empty + * @param key required to be not empty + */ + public Builder(@NonNull String screenKey, @NonNull String key) { + if (TextUtils.isEmpty(screenKey)) { + throw new IllegalArgumentException("screenKey cannot be empty"); + } + if (TextUtils.isEmpty(key)) { + throw new IllegalArgumentException("key cannot be empty"); + } + mScreenKey = screenKey; + mKey = key; + } + + /** + * Sets the preference title. + */ + @NonNull + public Builder setTitle(@Nullable String title) { + mTitle = title; + return this; + } + + /** + * Sets the preference summary. + */ + @NonNull + public Builder setSummary(@Nullable String summary) { + mSummary = summary; + return this; + } + + /** + * Sets the preference breadcrumbs (navigation context). + */ + @NonNull + public Builder setBreadcrumbs(@NonNull List<String> breadcrumbs) { + mBreadcrumbs = breadcrumbs; + return this; + } + + /** + * Sets the permissions required for reading this preference. + */ + @NonNull + public Builder setReadPermissions(@NonNull List<String> readPermissions) { + mReadPermissions = readPermissions; + return this; + } + + /** + * Sets the permissions required for writing this preference. + */ + @NonNull + public Builder setWritePermissions(@NonNull List<String> writePermissions) { + mWritePermissions = writePermissions; + return this; + } + + /** + * Set whether the preference is enabled. + */ + @NonNull + public Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + + /** + * Sets whether the preference is available. + */ + @NonNull + public Builder setAvailable(boolean available) { + mAvailable = available; + return this; + } + + /** + * Sets whether the preference is writable. + */ + @NonNull + public Builder setWritable(boolean writable) { + mWritable = writable; + return this; + } + + /** + * Sets whether the preference is restricted. + */ + @NonNull + public Builder setRestricted(boolean restricted) { + mRestricted = restricted; + return this; + } + + /** + * Sets the preference write-level sensitivity. + */ + @NonNull + public Builder setWriteSensitivity(@WriteSensitivity int sensitivity) { + mSensitivity = sensitivity; + return this; + } + + /** + * Sets the intent to launch the host app page for this preference. + */ + @NonNull + public Builder setLaunchIntent(@Nullable PendingIntent launchIntent) { + mLaunchIntent = launchIntent; + return this; + } + + /** + * Sets additional fields specific to this preference. Treat all data as optional. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** + * Constructs an immutable {@link SettingsPreferenceMetadata} object. + */ + @NonNull + public SettingsPreferenceMetadata build() { + return new SettingsPreferenceMetadata(this); + } + } +} diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java new file mode 100644 index 000000000000..f056e34a0dd2 --- /dev/null +++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java @@ -0,0 +1,220 @@ +/* + * 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.service.settings.preferences; + +import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.annotation.SuppressLint; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settingslib.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This objects represents a value that can be used for a particular settings preference. + * <p>The data type for the value will correspond to {@link #getType}. For possible types, see + * constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}. + * Depending on the type, the corresponding getter will contain its value. All other getters will + * return default values (boolean returns false, String returns null) so they should not be used. + * <p>See documentation on the constants for which getter method should be used. + */ +@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST) +public final class SettingsPreferenceValue implements Parcelable { + + @Type + private final int mType; + private final boolean mBooleanValue; + private final long mLongValue; + private final double mDoubleValue; + @Nullable + private final String mStringValue; + + /** + * Returns the type indicator for Preference value. + */ + @Type + public int getType() { + return mType; + } + + /** + * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}. + */ + public boolean getBooleanValue() { + return mBooleanValue; + } + + /** + * Returns the long value for Preference if type is {@link #TYPE_LONG}. + */ + public long getLongValue() { + return mLongValue; + } + + /** + * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}. + */ + public double getDoubleValue() { + return mDoubleValue; + } + + /** + * Returns the string value for Preference if type is {@link #TYPE_STRING}. + */ + @Nullable + public String getStringValue() { + return mStringValue; + } + + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_BOOLEAN, + TYPE_LONG, + TYPE_DOUBLE, + TYPE_STRING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + /** Value is of type boolean. Access via {@link #getBooleanValue}. */ + public static final int TYPE_BOOLEAN = 0; + /** Value is of type long. Access via {@link #getLongValue()}. */ + public static final int TYPE_LONG = 1; + /** Value is of type double. Access via {@link #getDoubleValue()}. */ + public static final int TYPE_DOUBLE = 2; + /** Value is of type string. Access via {@link #getStringValue}. */ + public static final int TYPE_STRING = 3; + + private SettingsPreferenceValue(@NonNull Builder builder) { + mType = builder.mType; + mBooleanValue = builder.mBooleanValue; + mLongValue = builder.mLongValue; + mDoubleValue = builder.mDoubleValue; + mStringValue = builder.mStringValue; + } + + private SettingsPreferenceValue(@NonNull Parcel in) { + mType = in.readInt(); + mBooleanValue = in.readBoolean(); + mLongValue = in.readLong(); + mDoubleValue = in.readDouble(); + mStringValue = in.readString8(); + } + + /** @hide */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeBoolean(mBooleanValue); + dest.writeLong(mLongValue); + dest.writeDouble(mDoubleValue); + dest.writeString8(mStringValue); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** + * Parcelable Creator for {@link SettingsPreferenceValue}. + */ + @NonNull + public static final Creator<SettingsPreferenceValue> CREATOR = new Creator<>() { + @Override + public SettingsPreferenceValue createFromParcel(@NonNull Parcel in) { + return new SettingsPreferenceValue(in); + } + + @Override + public SettingsPreferenceValue[] newArray(int size) { + return new SettingsPreferenceValue[size]; + } + }; + + /** + * Builder to construct {@link SettingsPreferenceValue}. + */ + public static final class Builder { + @Type + private final int mType; + private boolean mBooleanValue; + private long mLongValue; + private double mDoubleValue; + private String mStringValue; + + /** + * Create Builder instance. + * @param type type indicator for preference value + */ + public Builder(@Type int type) { + mType = type; + } + + /** + * Sets boolean value for Preference. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setBooleanValue(boolean booleanValue) { + mBooleanValue = booleanValue; + return this; + } + + /** + * Sets long value for Preference. + */ + @NonNull + public Builder setLongValue(long longValue) { + mLongValue = longValue; + return this; + } + + /** + * Sets floating point value for Preference. + */ + @NonNull + public Builder setDoubleValue(double doubleValue) { + mDoubleValue = doubleValue; + return this; + } + + /** + * Sets string value for Preference. + */ + @NonNull + public Builder setStringValue(@Nullable String stringValue) { + mStringValue = stringValue; + return this; + } + + /** + * Constructs an immutable {@link SettingsPreferenceValue} object. + */ + @NonNull + public SettingsPreferenceValue build() { + return new SettingsPreferenceValue(this); + } + } +} diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 8f112f338a00..4ff04d5c1fa6 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -447,7 +447,6 @@ public final class DisplayInfo implements Parcelable { && Objects.equals(displayCutout, other.displayCutout) && rotation == other.rotation && modeId == other.modeId - && renderFrameRate == other.renderFrameRate && hasArrSupport == other.hasArrSupport && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate) && defaultModeId == other.defaultModeId @@ -705,6 +704,9 @@ public final class DisplayInfo implements Parcelable { if (refreshRateOverride > 0) { return refreshRateOverride; } + if (renderFrameRate > 0) { + return renderFrameRate; + } if (supportedModes.length == 0) { return 0; } diff --git a/core/res/Android.bp b/core/res/Android.bp index 66c2e12f7cdf..73776f0d0b1d 100644 --- a/core/res/Android.bp +++ b/core/res/Android.bp @@ -158,6 +158,7 @@ android_app { flags_packages: [ "android.app.appfunctions.flags-aconfig", "android.app.contextualsearch.flags-aconfig", + "android.app.flags-aconfig", "android.appwidget.flags-aconfig", "android.content.pm.flags-aconfig", "android.provider.flags-aconfig", @@ -172,6 +173,7 @@ android_app { "com.android.hardware.input.input-aconfig", "aconfig_trade_in_mode_flags", "ranging_aconfig_flags", + "aconfig_settingslib_flags", ], } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index a6bf9151e0ab..0e4eb94feffb 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4463,6 +4463,18 @@ android:description="@string/permdesc_hideOverlayWindows" android:protectionLevel="normal" /> + <!-- Allows an app to enter Picture-in-Picture mode when the user is not explicitly requesting + it. This includes using {@link PictureInPictureParams.Builder#setAutoEnterEnabled} as well + as lifecycle methods such as {@link Activity#onUserLeaveHint} and {@link Activity#onPause} + to enter PiP when the user leaves the app. + This permission should only be used for certain PiP + <a href="{@docRoot}training/tv/get-started/multitasking#usage-types">usage types</a>. + @FlaggedApi(android.app.Flags.FLAG_ENABLE_TV_IMPLICIT_ENTER_PIP_RESTRICTION) + --> + <permission android:name="android.permission.TV_IMPLICIT_ENTER_PIP" + android:protectionLevel="normal" + android:featureFlag="android.app.enable_tv_implicit_enter_pip_restriction" /> + <!-- ================================== --> <!-- Permissions affecting the system wallpaper --> <!-- ================================== --> @@ -4969,6 +4981,27 @@ <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS" android:protectionLevel="signature|privileged|role" /> + <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST) + Allows an application to access the Settings Preference services to read settings exposed + by the system Settings app and system apps that contribute settings surfaced by the + Settings app. + <p>This allows the calling application to read settings values through the host + application, agnostic of underlying storage. --> + <permission android:name="android.permission.READ_SYSTEM_PREFERENCES" + android:protectionLevel="signature|privileged|role" + android:featureFlag="com.android.settingslib.flags.settings_catalyst" /> + + <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST) + Allows an application to access the Settings Preference services to write settings + values exposed by the system Settings app and system apps that contribute settings surfaced + in the Settings app. + <p>This allows the calling application to write settings values + through the host application, agnostic of underlying storage. + <p>Protection Level: signature|privileged|appop - appop to be added in followup --> + <permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES" + android:protectionLevel="signature|privileged" + android:featureFlag="com.android.settingslib.flags.settings_catalyst" /> + <!-- ========================================= --> <!-- Permissions for special development tools --> <!-- ========================================= --> diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt new file mode 100644 index 000000000000..a6de611cc077 --- /dev/null +++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt @@ -0,0 +1,472 @@ +/* + * 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.display + +import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM +import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP +import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT +import android.view.Display +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class DisplayTopologyTest { + private var topology = DisplayTopology() + + @Test + fun addOneDisplay() { + val displayId = 1 + val width = 800f + val height = 600f + + topology.addDisplay(displayId, width, height) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + + val display = topology.root!! + assertThat(display.displayId).isEqualTo(displayId) + assertThat(display.width).isEqualTo(width) + assertThat(display.height).isEqualTo(height) + assertThat(display.children).isEmpty() + } + + @Test + fun addTwoDisplays() { + val displayId1 = 1 + val width1 = 800f + val height1 = 600f + + val displayId2 = 2 + val width2 = 1000f + val height2 = 1500f + + topology.addDisplay(displayId1, width1, height1) + topology.addDisplay(displayId2, width2, height2) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + val display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + val display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).isEmpty() + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + } + + @Test + fun addManyDisplays() { + val displayId1 = 1 + val width1 = 800f + val height1 = 600f + + val displayId2 = 2 + val width2 = 1000f + val height2 = 1500f + + topology.addDisplay(displayId1, width1, height1) + topology.addDisplay(displayId2, width2, height2) + + val noOfDisplays = 30 + for (i in 3..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width1, height1) + } + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + val display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + val display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).hasSize(1) + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + + var display = display2 + for (i in 3..noOfDisplays) { + display = display.children[0] + assertThat(display.displayId).isEqualTo(i) + assertThat(display.width).isEqualTo(width1) + assertThat(display.height).isEqualTo(height1) + // The last display should have no children + assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.position).isEqualTo(POSITION_RIGHT) + assertThat(display.offset).isEqualTo(0) + } + } + + @Test + fun removeDisplays() { + val displayId1 = 1 + val width1 = 800f + val height1 = 600f + + val displayId2 = 2 + val width2 = 1000f + val height2 = 1500f + + topology.addDisplay(displayId1, width1, height1) + topology.addDisplay(displayId2, width2, height2) + + val noOfDisplays = 30 + for (i in 3..noOfDisplays) { + topology.addDisplay(/* displayId= */ i, width1, height1) + } + + var removedDisplays = arrayOf(20) + topology.removeDisplay(20) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + var display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + var display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).hasSize(1) + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + + var display = display2 + for (i in 3..noOfDisplays) { + if (i in removedDisplays) { + continue + } + display = display.children[0] + assertThat(display.displayId).isEqualTo(i) + assertThat(display.width).isEqualTo(width1) + assertThat(display.height).isEqualTo(height1) + // The last display should have no children + assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.position).isEqualTo(POSITION_RIGHT) + assertThat(display.offset).isEqualTo(0) + } + + topology.removeDisplay(22) + removedDisplays += 22 + topology.removeDisplay(23) + removedDisplays += 23 + topology.removeDisplay(25) + removedDisplays += 25 + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + + display1 = topology.root!! + assertThat(display1.displayId).isEqualTo(displayId1) + assertThat(display1.width).isEqualTo(width1) + assertThat(display1.height).isEqualTo(height1) + assertThat(display1.children).hasSize(1) + + display2 = display1.children[0] + assertThat(display2.displayId).isEqualTo(displayId2) + assertThat(display2.width).isEqualTo(width2) + assertThat(display2.height).isEqualTo(height2) + assertThat(display2.children).hasSize(1) + assertThat(display2.position).isEqualTo(POSITION_TOP) + assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2) + + display = display2 + for (i in 3..noOfDisplays) { + if (i in removedDisplays) { + continue + } + display = display.children[0] + assertThat(display.displayId).isEqualTo(i) + assertThat(display.width).isEqualTo(width1) + assertThat(display.height).isEqualTo(height1) + // The last display should have no children + assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0) + assertThat(display.position).isEqualTo(POSITION_RIGHT) + assertThat(display.offset).isEqualTo(0) + } + } + + @Test + fun removeAllDisplays() { + val displayId = 1 + val width = 800f + val height = 600f + + topology.addDisplay(displayId, width, height) + topology.removeDisplay(displayId) + + assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) + assertThat(topology.root).isNull() + } + + @Test + fun removeDisplayThatDoesNotExist() { + val displayId = 1 + val width = 800f + val height = 600f + + topology.addDisplay(displayId, width, height) + topology.removeDisplay(3) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId) + + val display = topology.root!! + assertThat(display.displayId).isEqualTo(displayId) + assertThat(display.width).isEqualTo(width) + assertThat(display.height).isEqualTo(height) + assertThat(display.children).isEmpty() + } + + @Test + fun removePrimaryDisplay() { + val displayId1 = 1 + val displayId2 = 2 + val width = 800f + val height = 600f + + topology = DisplayTopology(/* root= */ null, displayId2) + topology.addDisplay(displayId1, width, height) + topology.addDisplay(displayId2, width, height) + topology.removeDisplay(displayId2) + + assertThat(topology.primaryDisplayId).isEqualTo(displayId1) + val display = topology.root!! + assertThat(display.displayId).isEqualTo(displayId1) + assertThat(display.width).isEqualTo(width) + assertThat(display.height).isEqualTo(height) + assertThat(display.children).isEmpty() + } + + @Test + fun normalization_noOverlaps_leavesTopologyUnchanged() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 600f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) + display1.addChild(display3) + + val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display2.addChild(display4) + + topology = DisplayTopology(display1, primaryDisplayId) + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(600f) + assertThat(actualDisplay1.children).hasSize(2) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(600f) + assertThat(actualDisplay2.height).isEqualTo(200f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(1) + + val actualDisplay3 = actualDisplay1.children[1] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay3.offset).isEqualTo(400f) + assertThat(actualDisplay3.children).isEmpty() + + val actualDisplay4 = actualDisplay2.children[0] + assertThat(actualDisplay4.displayId).isEqualTo(4) + assertThat(actualDisplay4.width).isEqualTo(200f) + assertThat(actualDisplay4.height).isEqualTo(600f) + assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay4.offset).isEqualTo(0f) + assertThat(actualDisplay4.children).isEmpty() + } + + @Test + fun normalization_moveDisplayWithoutReparenting() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 600f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) + display1.addChild(display3) + + val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display2.addChild(display4) + + topology = DisplayTopology(display1, primaryDisplayId) + // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent. + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(600f) + assertThat(actualDisplay1.children).hasSize(1) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(200f) + assertThat(actualDisplay2.height).isEqualTo(600f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(2) + + val actualDisplay3 = actualDisplay2.children[1] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay3.offset).isEqualTo(10f) + assertThat(actualDisplay3.children).isEmpty() + + val actualDisplay4 = actualDisplay2.children[0] + assertThat(actualDisplay4.displayId).isEqualTo(4) + assertThat(actualDisplay4.width).isEqualTo(200f) + assertThat(actualDisplay4.height).isEqualTo(600f) + assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay4.offset).isEqualTo(210f) + assertThat(actualDisplay4.children).isEmpty() + } + + @Test + fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 50f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) + display1.addChild(display3) + + topology = DisplayTopology(display1, primaryDisplayId) + // Display 3 gets moved and its left side is still on the same line as the right side + // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2 + // becomes its new parent. + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(50f) + assertThat(actualDisplay1.children).hasSize(1) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(600f) + assertThat(actualDisplay2.height).isEqualTo(200f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(1) + + val actualDisplay3 = actualDisplay2.children[0] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_BOTTOM) + assertThat(actualDisplay3.offset).isEqualTo(0f) + assertThat(actualDisplay3.children).isEmpty() + } + + @Test + fun normalization_moveAndReparentDisplay() { + val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, + /* height= */ 600f, /* position= */ 0, /* offset= */ 0f) + + val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display1.addChild(display2) + + val primaryDisplayId = 3 + val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, + /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) + display1.addChild(display3) + + val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, + /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) + display2.addChild(display4) + + topology = DisplayTopology(display1, primaryDisplayId) + topology.normalize() + + assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId) + + val actualDisplay1 = topology.root!! + assertThat(actualDisplay1.displayId).isEqualTo(1) + assertThat(actualDisplay1.width).isEqualTo(200f) + assertThat(actualDisplay1.height).isEqualTo(600f) + assertThat(actualDisplay1.children).hasSize(1) + + val actualDisplay2 = actualDisplay1.children[0] + assertThat(actualDisplay2.displayId).isEqualTo(2) + assertThat(actualDisplay2.width).isEqualTo(200f) + assertThat(actualDisplay2.height).isEqualTo(600f) + assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay2.offset).isEqualTo(0f) + assertThat(actualDisplay2.children).hasSize(1) + + val actualDisplay3 = actualDisplay2.children[0] + assertThat(actualDisplay3.displayId).isEqualTo(3) + assertThat(actualDisplay3.width).isEqualTo(600f) + assertThat(actualDisplay3.height).isEqualTo(200f) + assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay3.offset).isEqualTo(400f) + assertThat(actualDisplay3.children).hasSize(1) + + val actualDisplay4 = actualDisplay3.children[0] + assertThat(actualDisplay4.displayId).isEqualTo(4) + assertThat(actualDisplay4.width).isEqualTo(200f) + assertThat(actualDisplay4.height).isEqualTo(600f) + assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT) + assertThat(actualDisplay4.offset).isEqualTo(-400f) + assertThat(actualDisplay4.children).isEmpty() + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java index 4c5b7e508e34..8932cf1ba552 100644 --- a/core/tests/coretests/src/android/view/DisplayInfoTest.java +++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java @@ -78,6 +78,23 @@ public class DisplayInfoTest { } @Test + public void testRefreshRateOverride_keepsDisplyInfosEqualWhenOverrideIsSame() { + Display.Mode mode = new Display.Mode( + /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120); + DisplayInfo displayInfo1 = new DisplayInfo(); + setSupportedMode(displayInfo1, mode); + displayInfo1.renderFrameRate = 60; + displayInfo1.refreshRateOverride = 30; + + DisplayInfo displayInfo2 = new DisplayInfo(); + setSupportedMode(displayInfo2, mode); + displayInfo2.renderFrameRate = 30; + displayInfo2.refreshRateOverride = 30; + + assertTrue(displayInfo1.equals(displayInfo2)); + } + + @Test public void testRefreshRateOverride_makeDisplayInfosDifferent() { Display.Mode mode = new Display.Mode( /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120); diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 4c47de0ca754..d55a71e21931 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -1761,7 +1761,7 @@ public abstract class ColorSpace { if (Flags.displayBt2020Colorspace()) { sNamedColorSpaceMap.put(Named.DISPLAY_BT2020.ordinal(), new ColorSpace.Rgb( - "BT 2020", + "Display BT. 2020", BT2020_PRIMARIES, ILLUMINANT_D65, null, diff --git a/keystore/java/android/security/keystore/KeyStoreManager.java b/keystore/java/android/security/keystore/KeyStoreManager.java index 197aaba4bcb5..e6091c1da8a5 100644 --- a/keystore/java/android/security/keystore/KeyStoreManager.java +++ b/keystore/java/android/security/keystore/KeyStoreManager.java @@ -49,7 +49,7 @@ import java.util.List; */ @FlaggedApi(android.security.Flags.FLAG_KEYSTORE_GRANT_API) @SystemService(Context.KEYSTORE_SERVICE) -public class KeyStoreManager { +public final class KeyStoreManager { private static final String TAG = "KeyStoreManager"; private static final Object sInstanceLock = new Object(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index cefcb757690f..01c680dc8325 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -205,11 +205,6 @@ class DesktopMixedTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: TransitionFinishCallback, ): Boolean { - val launchChange = findDesktopTaskChange(info, pending.launchingTask) - if (launchChange == null) { - logV("No launch Change, returning") - return false - } // Check if there's also an immersive change during this launch. val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask -> findDesktopTaskChange(info, exitingTask) @@ -217,6 +212,13 @@ class DesktopMixedTransitionHandler( val minimizeChange = pending.minimizingTask?.let { minimizingTask -> findDesktopTaskChange(info, minimizingTask) } + val launchChange = findDesktopTaskChange(info, pending.launchingTask) + if (launchChange == null) { + check(minimizeChange == null) + check(immersiveExitChange == null) + logV("No launch Change, returning") + return false + } var subAnimationCount = -1 var combinedWct: WindowContainerTransaction? = null diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp index c6cee07d1946..5ab5a7a59c2a 100644 --- a/libs/appfunctions/Android.bp +++ b/libs/appfunctions/Android.bp @@ -18,10 +18,10 @@ package { } java_sdk_library { - name: "com.google.android.appfunctions.sidecar", + name: "com.android.extensions.appfunctions", owner: "google", srcs: ["java/**/*.java"], - api_packages: ["com.google.android.appfunctions.sidecar"], + api_packages: ["com.android.extensions.appfunctions"], dex_preopt: { enabled: false, }, @@ -31,9 +31,9 @@ java_sdk_library { } prebuilt_etc { - name: "appfunctions.sidecar.xml", + name: "appfunctions.extension.xml", system_ext_specific: true, sub_dir: "permissions", - src: "appfunctions.sidecar.xml", + src: "appfunctions.extension.xml", filename_from_src: true, } diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index faf84a8ab5ac..0eda10112ee8 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -1,9 +1,9 @@ // Signature format: 2.0 -package com.google.android.appfunctions.sidecar { +package com.android.extensions.appfunctions { public final class AppFunctionManager { ctor public AppFunctionManager(android.content.Context); - method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.ExecuteAppFunctionResponse>); method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); @@ -15,7 +15,7 @@ package com.google.android.appfunctions.sidecar { public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.android.extensions.appfunctions.ExecuteAppFunctionResponse>); field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -29,9 +29,9 @@ package com.google.android.appfunctions.sidecar { public static final class ExecuteAppFunctionRequest.Builder { ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build(); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); - method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build(); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); } public final class ExecuteAppFunctionResponse { @@ -41,13 +41,13 @@ package com.google.android.appfunctions.sidecar { method public int getResultCode(); method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); method public boolean isSuccess(); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); - method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); + method @NonNull public static com.android.extensions.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); + method @NonNull public static com.android.extensions.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); field public static final int ERROR_CATEGORY_APP = 3; // 0x3 field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1 field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2 field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0 - field public static final String PROPERTY_RETURN_VALUE = "returnValue"; + field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8 field public static final int RESULT_CANCELLED = 2001; // 0x7d1 field public static final int RESULT_DENIED = 1000; // 0x3e8 diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.extension.xml index bef8b6ec7ce6..dd09cc39d12f 100644 --- a/libs/appfunctions/appfunctions.sidecar.xml +++ b/libs/appfunctions/appfunctions.extension.xml @@ -16,6 +16,6 @@ --> <permissions> <library - name="com.google.android.appfunctions.sidecar" - file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/> + name="com.android.extensions.appfunctions" + file="/system_ext/framework/com.android.extensions.appfunctions.jar"/> </permissions>
\ No newline at end of file diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java index 2075104ff868..d64593d8ff5f 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.Manifest; import android.annotation.CallbackExecutor; diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java index 0dc87e45b7e3..1a4d9da8bd63 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; @@ -26,7 +26,6 @@ import android.content.Intent; import android.os.Binder; import android.os.CancellationSignal; import android.os.IBinder; -import android.util.Log; import java.util.function.Consumer; diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java index 593c5213dd52..baddc245f0f1 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; import android.app.appsearch.GenericDocument; @@ -91,8 +91,8 @@ public final class ExecuteAppFunctionRequest { * Returns the function parameters. The key is the parameter name, and the value is the * parameter value. * - * <p>The bundle may have missing parameters. Developers are advised to implement defensive - * handling measures. + * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to + * implement defensive handling measures. * * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java index 4e88fb025a9d..7c5ddcd9edfb 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.IntDef; import android.annotation.NonNull; @@ -48,7 +48,7 @@ public final class ExecuteAppFunctionResponse { * * <p>See {@link #getResultDocument} for more information on extracting the return value. */ - public static final String PROPERTY_RETURN_VALUE = "returnValue"; + public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue"; /** * The call was successful. diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java index b1b05f79f33f..56f2725fccc7 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java +++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar; +package com.android.extensions.appfunctions; import android.annotation.NonNull; @@ -28,26 +28,24 @@ public final class SidecarConverter { private SidecarConverter() {} /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionRequest} * * @hide */ @NonNull public static android.app.appfunctions.ExecuteAppFunctionRequest getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { - return new - android.app.appfunctions.ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} - * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link + * android.app.appfunctions.ExecuteAppFunctionResponse} * * @hide */ @@ -59,15 +57,13 @@ public final class SidecarConverter { response.getResultDocument(), response.getExtras()); } else { return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); + response.getResultCode(), response.getErrorMessage(), response.getExtras()); } } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's + * {@link ExecuteAppFunctionRequest} * * @hide */ @@ -75,16 +71,15 @@ public final class SidecarConverter { public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { return new ExecuteAppFunctionRequest.Builder( - request.getTargetPackageName(), - request.getFunctionIdentifier()) + request.getTargetPackageName(), request.getFunctionIdentifier()) .setExtras(request.getExtras()) .setParameters(request.getParameters()) .build(); } /** - * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} - * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into + * sidecar's {@link ExecuteAppFunctionResponse} * * @hide */ @@ -96,9 +91,7 @@ public final class SidecarConverter { response.getResultDocument(), response.getExtras()); } else { return ExecuteAppFunctionResponse.newFailure( - response.getResultCode(), - response.getErrorMessage(), - response.getExtras()); + response.getResultCode(), response.getErrorMessage(), response.getExtras()); } } } diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp index 6f5eff305d8d..db79675ae9f7 100644 --- a/libs/appfunctions/tests/Android.bp +++ b/libs/appfunctions/tests/Android.bp @@ -25,7 +25,7 @@ android_test { "androidx.test.rules", "androidx.test.ext.junit", "androidx.core_core-ktx", - "com.google.android.appfunctions.sidecar.impl", + "com.android.extensions.appfunctions.impl", "junit", "kotlin-test", "mockito-target-extended-minus-junit4", diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt index 264f84209caf..6118e6ce4ab2 100644 --- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt +++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.google.android.appfunctions.sidecar.tests +package com.android.extensions.appfunctions.tests import android.app.appfunctions.ExecuteAppFunctionRequest import android.app.appfunctions.ExecuteAppFunctionResponse import android.app.appsearch.GenericDocument import android.os.Bundle import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.android.appfunctions.sidecar.SidecarConverter +import com.android.extensions.appfunctions.SidecarConverter import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -60,7 +60,7 @@ class SidecarConverterTest { .setPropertyLong("testLong", 23) .build() val sidecarRequest = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder( + com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder( "targetPkg", "targetFunctionId" ) @@ -129,8 +129,11 @@ class SidecarConverterTest { GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true) .build() - val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse - .newSuccess(resultGd, null) + val sidecarResponse = + com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newSuccess( + resultGd, + null + ) val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse( sidecarResponse @@ -151,7 +154,7 @@ class SidecarConverterTest { fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() { val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build() val sidecarResponse = - com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure( + com.android.extensions.appfunctions.ExecuteAppFunctionResponse.newFailure( ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR, null, null diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java index b08a86ee8f46..bd65b2ecb76a 100644 --- a/media/java/android/media/MediaFormat.java +++ b/media/java/android/media/MediaFormat.java @@ -17,6 +17,7 @@ package android.media; import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC; +import static android.media.codec.Flags.FLAG_NUM_INPUT_SLOTS; import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST; import static android.media.codec.Flags.FLAG_APV_SUPPORT; @@ -1777,6 +1778,17 @@ public final class MediaFormat { public static final String KEY_SECURITY_MODEL = "security-model"; /** + * A key describing the number of slots used in the codec. When present in input format, + * the associated value indicates the number of input slots. The entry is set by the codec + * if configured with (@link MediaCodec#CONFIGURE_FLAG_BLOCK_MODEL), and will be ignored if set + * by the application. + * <p> + * The associated value is an integer. + */ + @FlaggedApi(FLAG_NUM_INPUT_SLOTS) + public static final String KEY_NUM_SLOTS = "num-slots"; + + /** * QpOffsetRect constitutes the metadata required for encoding a region of interest in an * image or a video frame. The region of interest is represented by a rectangle. The four * integer coordinates of the rectangle are stored in fields left, top, right, bottom. diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java index c1d73f9033cf..8521d1c472a8 100644 --- a/media/java/android/media/audio/common/AidlConversion.java +++ b/media/java/android/media/audio/common/AidlConversion.java @@ -705,6 +705,10 @@ public class AidlConversion { aidl.type = AudioDeviceType.OUT_BROADCAST; aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE; break; + case AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP: + aidl.type = AudioDeviceType.OUT_MULTICHANNEL_GROUP; + aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL; + break; case AudioSystem.DEVICE_IN_BUILTIN_MIC: aidl.type = AudioDeviceType.IN_MICROPHONE; break; diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 90b4aba690d0..52a21e241ba8 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -79,7 +79,7 @@ flag { flag { name: "update_client_profile_priority" - namespace: "media" + namespace: "media_solutions" description : "Feature flag to add updateResourcePriority api to MediaCas" bug: "300565729" } diff --git a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java index 09573909c288..d9a1221e529c 100644 --- a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java +++ b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java @@ -18,6 +18,7 @@ package android.media.audio.common; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -504,6 +505,27 @@ public final class AidlConversionUnitTests { assertEquals(AudioDeviceType.OUT_DEVICE, port.ext.getDevice().device.type.type); } + @Test + public void testAudioDeviceDescriptionConversion() { + for (int nativeDeviceType : AudioSystem.DEVICE_OUT_ALL_SET) { + assertNotEquals( + AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType) + .type, + AudioDeviceType.NONE); + } + + for (int nativeDeviceType : AudioSystem.DEVICE_IN_ALL_SET) { + if (nativeDeviceType == AudioSystem.DEVICE_IN_COMMUNICATION + || nativeDeviceType == AudioSystem.DEVICE_IN_AMBIENT) { + continue; + } + assertNotEquals( + AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType) + .type, + AudioDeviceType.NONE); + } + } + private static AudioFormatDescription createPcm16FormatAidl() { final AudioFormatDescription aidl = new AudioFormatDescription(); aidl.type = AudioFormatType.PCM; diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index e1418678a9f8..b2dcb7fa4f53 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -61,7 +61,7 @@ android_library { "SettingsLibUtils", "SettingsLibZeroStatePreference", "settingslib_media_flags_lib", - "settingslib_flags_lib", + "aconfig_settingslib_flags_java_lib", ], plugins: ["androidx.room_room-compiler-plugin"], @@ -107,20 +107,6 @@ java_aconfig_library { aconfig_declarations: "settingslib_media_flags", } -aconfig_declarations { - name: "settingslib_flags", - package: "com.android.settingslib.flags", - container: "system", - srcs: [ - "aconfig/settingslib.aconfig", - ], -} - -java_aconfig_library { - name: "settingslib_flags_lib", - aconfig_declarations: "settingslib_flags", -} - soong_config_module_type { name: "avatar_picker_java_defaults", module_type: "java_defaults", diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java index 979ff96be3f7..993555e78bea 100644 --- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java +++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java @@ -37,7 +37,7 @@ import com.google.android.material.button.MaterialButton; /** * A preference handled a button */ -public class ButtonPreference extends Preference { +public class ButtonPreference extends Preference implements GroupSectionDividerMixin { enum ButtonStyle { FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled), diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java index d60290ed91ef..37f47543c536 100644 --- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java @@ -43,7 +43,7 @@ import java.net.URISyntaxException; * A custom preference acting as "footer" of a page. It has a field for icon and text. It is added * to screen as the last preference. */ -public class FooterPreference extends Preference { +public class FooterPreference extends Preference implements GroupSectionDividerMixin { private static final String TAG = "FooterPreference"; public static final String KEY_FOOTER = "footer_preference"; diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt index c1578ef69635..1f8cfb5e432e 100644 --- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt +++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt @@ -34,7 +34,7 @@ class StatusBannerPreference @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : Preference(context, attrs, defStyleAttr, defStyleRes) { +) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin { enum class BannerStatus { GENERIC, diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt index 5be56f8ebc86..9764e64b8509 100644 --- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt +++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt @@ -31,7 +31,7 @@ open class TopIntroPreference @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : Preference(context, attrs, defStyleAttr, defStyleRes) { +) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin { private var isCollapsable: Boolean = false private var minLines: Int = 2 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt index d58e1bfbda83..eeab232542c0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt @@ -288,7 +288,7 @@ private fun DragHandle(dialog: Dialog) { Modifier.padding(top = 16.dp, bottom = 6.dp) .semantics { contentDescription = dragHandleContentDescription } .clickable { dialog.dismiss() }, - color = MaterialTheme.colorScheme.outlineVariant, + color = MaterialTheme.colorScheme.onSurfaceVariant, shape = MaterialTheme.shapes.extraLarge, ) { Box(Modifier.size(width = 32.dp, height = 4.dp)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index 8ccaf6bc0651..0f631509bfba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -300,7 +300,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase { mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0", UserHandle.USER_CURRENT); verifyActivityDetails("abc/.def"); - assertThat(mController.isEnabledForLockScreenButton()).isFalse(); + assertThat(mController.isEnabledForLockScreenButton()).isTrue(); assertThat(mController.isAllowedOnLockScreen()).isTrue(); assertThat(mController.isAbleToLaunchScannerActivity()).isTrue(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 5cba325450e6..03feceb7c15a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -318,6 +318,63 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } } + @Test + fun qqsMediaExpansion_collapsedMediaInLandscape() = + with(kosmos) { + testScope.testWithinLifecycle { + setCollapsedMediaInLandscape(true) + setMediaState(ACTIVE_MEDIA) + + setConfigurationForMediaInRow(mediaInRow = false) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + + setConfigurationForMediaInRow(mediaInRow = true) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED) + } + } + + @Test + fun qqsMediaExpansion_notCollapsedMediaInLandscape_alwaysExpanded() = + with(kosmos) { + testScope.testWithinLifecycle { + setCollapsedMediaInLandscape(false) + setMediaState(ACTIVE_MEDIA) + + setConfigurationForMediaInRow(mediaInRow = false) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + + setConfigurationForMediaInRow(mediaInRow = true) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + } + } + + @Test + fun qqsMediaExpansion_reactsToChangesInCollapsedMediaInLandscape() = + with(kosmos) { + testScope.testWithinLifecycle { + setConfigurationForMediaInRow(mediaInRow = true) + setMediaState(ACTIVE_MEDIA) + + setCollapsedMediaInLandscape(false) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED) + + setCollapsedMediaInLandscape(true) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED) + } + } + private fun TestScope.setMediaState(state: MediaState) { with(kosmos) { val activeMedia = state == ACTIVE_MEDIA @@ -331,6 +388,14 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() runCurrent() } + private fun TestScope.setCollapsedMediaInLandscape(collapsed: Boolean) { + with(kosmos) { + overrideResource(R.bool.config_quickSettingsMediaLandscapeCollapsed, collapsed) + fakeConfigurationRepository.onAnyConfigurationChange() + } + runCurrent() + } + companion object { private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 6b371d74eacc..9b47eaddffd6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.user.data.repository import android.app.admin.devicePolicyManager import android.content.pm.UserInfo +import android.internal.statusbar.fakeStatusBarService import android.os.UserHandle import android.os.UserManager import android.provider.Settings @@ -61,6 +62,7 @@ class UserRepositoryImplTest : SysuiTestCase() { private val globalSettings = kosmos.fakeGlobalSettings private val broadcastDispatcher = kosmos.broadcastDispatcher private val devicePolicyManager = kosmos.devicePolicyManager + private val statusBarService = kosmos.fakeStatusBarService @Mock private lateinit var manager: UserManager @@ -323,6 +325,8 @@ class UserRepositoryImplTest : SysuiTestCase() { tracker = tracker, broadcastDispatcher = broadcastDispatcher, devicePolicyManager = devicePolicyManager, + resources = context.resources, + statusBarService = statusBarService, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt index 26439df45ba3..f70b42638cda 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt @@ -49,35 +49,78 @@ class UserLogoutInteractorTest : SysuiTestCase() { @Before fun setUp() { userRepository.setUserInfos(USER_INFOS) - runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[1]) } + runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[2]) } + userRepository.setLogoutToSystemUserEnabled(false) + userRepository.setSecondaryUserLogoutEnabled(false) } @Test - fun logOut_doesNothing_whenAdminDisabledSecondaryLogout() { + fun logOut_doesNothing_whenBothLogoutOptionsAreDisabled() { testScope.runTest { val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) - val lastLogoutCount = userRepository.logOutSecondaryUserCallCount - userRepository.setSecondaryUserLogoutEnabled(false) + val secondaryUserLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount assertThat(isLogoutEnabled).isFalse() underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount) + .isEqualTo(secondaryUserLogoutCount) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) + } + } + + @Test + fun logOut_logsOutSecondaryUser_whenAdminEnabledSecondaryLogout() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setSecondaryUserLogoutEnabled(true) + assertThat(isLogoutEnabled).isTrue() + underTest.logOut() + assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) + } + } + + @Test + fun logOut_logsOutToSystemUser_whenLogoutToSystemUserIsEnabled() { + testScope.runTest { + val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) + val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setLogoutToSystemUserEnabled(true) + assertThat(isLogoutEnabled).isTrue() + underTest.logOut() assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount + 1) } } @Test - fun logOut_logsOut_whenAdminEnabledSecondaryLogout() { + fun logOut_secondaryUserTakesPrecedence() { testScope.runTest { val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled) val lastLogoutCount = userRepository.logOutSecondaryUserCallCount + val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount + userRepository.setLogoutToSystemUserEnabled(true) userRepository.setSecondaryUserLogoutEnabled(true) assertThat(isLogoutEnabled).isTrue() underTest.logOut() assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1) + assertThat(userRepository.logOutToSystemUserCallCount) + .isEqualTo(logoutToSystemUserCount) } } companion object { private val USER_INFOS = - listOf(UserInfo(0, "System user", 0), UserInfo(10, "Regular user", 0)) + listOf( + UserInfo(0, "System user", 0), + UserInfo(10, "Regular user", 0), + UserInfo(11, "Secondary user", 0), + ) } } diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 82c8c44f1efe..0854eb46ffdd 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -1086,4 +1086,9 @@ enable the desktop specific features. --> <bool name="config_enableDesktopFeatureSet">false</bool> + + <!-- + Whether the user switching can only happen by logging out and going through the system user (login screen). + --> + <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java index 765b45bdbf2e..bab88c0b0bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java @@ -159,7 +159,7 @@ public class QRCodeScannerController implements * Returns true if lock screen entry point for QR Code Scanner is to be enabled. */ public boolean isEnabledForLockScreenButton() { - return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen(); + return isAbleToLaunchScannerActivity() && isAllowedOnLockScreen(); } /** Returns whether the QR scanner button is allowed on lockscreen. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index 624cf306a3b2..e912a0c7faa6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -63,6 +63,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository import com.android.systemui.util.LargeScreenUtils import com.android.systemui.util.asIndenting +import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.printSection import com.android.systemui.util.println import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow @@ -278,6 +279,24 @@ constructor( private val mediaSuddenlyAppearingInLandscape: Boolean get() = !qqsMediaInRow && qsMediaInRow + private val collapsedLandscapeMedia by + hydrator.hydratedStateOf( + traceName = "collapsedLandscapeMedia", + initialValue = resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed), + source = + configurationInteractor.onAnyConfigurationChange.emitOnStart().map { + resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed) + }, + ) + + private val qqsMediaExpansion: Float + get() = + if (qqsMediaInRow && collapsedLandscapeMedia) { + MediaHostState.COLLAPSED + } else { + MediaHostState.EXPANDED + } + private var qsBounds by mutableStateOf(Rect()) private val constrainedSquishinessFraction: Float @@ -379,6 +398,7 @@ constructor( initMediaHosts() // init regardless of using media (same as current QS). coroutineScope { launch { hydrateSquishinessInteractor() } + launch { hydrateQqsMediaExpansion() } launch { hydrator.activate() } launch { containerViewModel.activate() } launch { qqsMediaInRowViewModel.activate() } @@ -389,7 +409,7 @@ constructor( private fun initMediaHosts() { qqsMediaHost.apply { - expansion = MediaHostState.EXPANDED + expansion = qqsMediaExpansion showsOnlyActiveMedia = true init(MediaHierarchyManager.LOCATION_QQS) } @@ -405,6 +425,10 @@ constructor( .collect { squishinessInteractor.setSquishinessValue(it) } } + private suspend fun hydrateQqsMediaExpansion() { + snapshotFlow { qqsMediaExpansion }.collect { qqsMediaHost.expansion = it } + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.asIndenting().run { printSection("Quick Settings state") { @@ -448,6 +472,8 @@ constructor( println("qqsMediaInRow", qqsMediaInRow) println("qsMediaVisible", qsMediaVisible) println("qsMediaInRow", qsMediaInRow) + println("collapsedLandscapeMedia", collapsedLandscapeMedia) + println("qqsMediaExpansion", qqsMediaExpansion) } } } diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index f20ce63467f7..e9a33e062c60 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -23,11 +23,13 @@ import android.app.admin.DevicePolicyManager import android.content.Context import android.content.IntentFilter import android.content.pm.UserInfo +import android.content.res.Resources import android.os.UserHandle import android.os.UserManager import android.provider.Settings import androidx.annotation.VisibleForTesting import com.android.app.tracing.coroutines.launchTraced as launch +import com.android.internal.statusbar.IStatusBarService import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -109,6 +111,9 @@ interface UserRepository { /** Whether logout for secondary users is enabled by admin device policy. */ val isSecondaryUserLogoutEnabled: StateFlow<Boolean> + /** Whether logout into system user is enabled. */ + val isLogoutToSystemUserEnabled: StateFlow<Boolean> + /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */ fun refreshUsers() @@ -121,6 +126,9 @@ interface UserRepository { /** Performs logout logout for secondary users. */ suspend fun logOutSecondaryUser() + /** Performs logout into the system user. */ + suspend fun logOutToSystemUser() + /** * Returns the user ID of the "main user" of the device. This user may have access to certain * features which are limited to at most one user. There will never be more than one main user @@ -143,6 +151,7 @@ class UserRepositoryImpl @Inject constructor( @Application private val appContext: Context, + @Main private val resources: Resources, private val manager: UserManager, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, @@ -151,6 +160,7 @@ constructor( private val tracker: UserTracker, private val devicePolicyManager: DevicePolicyManager, private val broadcastDispatcher: BroadcastDispatcher, + private val statusBarService: IStatusBarService, ) : UserRepository { private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> = @@ -275,12 +285,34 @@ constructor( .stateIn(applicationScope, SharingStarted.Eagerly, false) @SuppressLint("MissingPermission") + override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = + selectedUser + .flatMapLatestConflated { selectedUser -> + if (selectedUser.isEligibleForLogout()) { + flowOf( + resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen) + ) + } else { + flowOf(false) + } + } + .stateIn(applicationScope, SharingStarted.Eagerly, false) + + @SuppressLint("MissingPermission") override suspend fun logOutSecondaryUser() { if (isSecondaryUserLogoutEnabled.value) { withContext(backgroundDispatcher) { devicePolicyManager.logoutUser() } } } + override suspend fun logOutToSystemUser() { + // TODO(b/377493351) : start using proper logout API once it is available. + // Using reboot is a temporary solution. + if (isLogoutToSystemUserEnabled.value) { + withContext(backgroundDispatcher) { statusBarService.reboot(false) } + } + } + @SuppressLint("MissingPermission") override fun refreshUsers() { applicationScope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt index 154f1dc3e747..f2dd25fecf08 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt @@ -23,7 +23,10 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.user.data.repository.UserRepository import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn /** Encapsulates business logic to for the logout. */ @SysUISingleton @@ -33,11 +36,22 @@ constructor( private val userRepository: UserRepository, @Application private val applicationScope: CoroutineScope, ) { - val isLogoutEnabled: StateFlow<Boolean> = userRepository.isSecondaryUserLogoutEnabled + + val isLogoutEnabled: StateFlow<Boolean> = + combine( + userRepository.isSecondaryUserLogoutEnabled, + userRepository.isLogoutToSystemUserEnabled, + Boolean::or, + ) + .stateIn(applicationScope, SharingStarted.Eagerly, false) fun logOut() { - if (userRepository.isSecondaryUserLogoutEnabled.value) { - applicationScope.launch { userRepository.logOutSecondaryUser() } + applicationScope.launch { + if (userRepository.isSecondaryUserLogoutEnabled.value) { + userRepository.logOutSecondaryUser() + } else if (userRepository.isLogoutToSystemUserEnabled.value) { + userRepository.logOutToSystemUser() + } } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt index d1b613fe7f6e..f63698a3f2f9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt @@ -21,6 +21,7 @@ import android.content.res.mainResources import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.runCurrent import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment import com.android.systemui.shade.data.repository.shadeRepository @@ -56,4 +57,5 @@ fun Kosmos.setConfigurationForMediaInRow(mediaInRow: Boolean) { } mainResources.configuration.updateFrom(config) fakeConfigurationRepository.onConfigurationChange(config) + runCurrent() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 1808a5f99f4e..85d582a27faf 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -72,6 +72,10 @@ class FakeUserRepository @Inject constructor() : UserRepository { override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> = _isSecondaryUserLogoutEnabled.asStateFlow() + private val _isLogoutToSystemUserEnabled = MutableStateFlow<Boolean>(false) + override val isLogoutToSystemUserEnabled: StateFlow<Boolean> = + _isLogoutToSystemUserEnabled.asStateFlow() + override var mainUserId: Int = MAIN_USER_ID override var lastSelectedNonGuestUserId: Int = mainUserId @@ -123,6 +127,17 @@ class FakeUserRepository @Inject constructor() : UserRepository { logOutSecondaryUserCallCount++ } + fun setLogoutToSystemUserEnabled(logoutEnabled: Boolean) { + _isLogoutToSystemUserEnabled.value = logoutEnabled + } + + var logOutToSystemUserCallCount: Int = 0 + private set + + override suspend fun logOutToSystemUser() { + logOutToSystemUserCallCount++ + } + fun setUserInfos(infos: List<UserInfo>) { _userInfos.value = infos } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index d8f2b705d539..3ed8b0a748e1 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -16,7 +16,6 @@ package android.platform.test.ravenwood; import static android.os.Process.FIRST_APPLICATION_UID; -import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.SYSTEM; import android.annotation.NonNull; @@ -61,17 +60,14 @@ public final class RavenwoodConfig { * Unless the test author requests differently, run as "nobody", and give each collection of * tests its own unique PID. */ - int mUid = NOBODY_UID; + int mUid = FIRST_APPLICATION_UID; int mPid = sNextPid.getAndIncrement(); String mTestPackageName; String mTargetPackageName; - int mMinSdkLevel; int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT; - boolean mProvideMainThread = false; - final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties(); final List<Class<?>> mServicesRequired = new ArrayList<>(); @@ -108,20 +104,18 @@ public final class RavenwoodConfig { } /** - * Configure the identity of this process to be the system UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessSystem() { - mConfig.mUid = SYSTEM_UID; return this; } /** - * Configure the identity of this process to be an app UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessApp() { - mConfig.mUid = FIRST_APPLICATION_UID; return this; } @@ -144,14 +138,6 @@ public final class RavenwoodConfig { } /** - * Configure the min SDK level of the test. - */ - public Builder setMinSdkLevel(int sdkLevel) { - mConfig.mMinSdkLevel = sdkLevel; - return this; - } - - /** * Configure the target SDK level of the test. */ public Builder setTargetSdkLevel(int sdkLevel) { @@ -160,14 +146,10 @@ public final class RavenwoodConfig { } /** - * Configure a "main" thread to be available for the duration of the test, as defined - * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. - * - * @deprecated + * @deprecated no longer used. Main thread is always available. */ @Deprecated public Builder setProvideMainThread(boolean provideMainThread) { - mConfig.mProvideMainThread = provideMainThread; return this; } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 3d6ac0f37050..bfa3802ce583 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -112,20 +112,18 @@ public final class RavenwoodRule implements TestRule { } /** - * Configure the identity of this process to be the system UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessSystem() { - mBuilder.setProcessSystem(); return this; } /** - * Configure the identity of this process to be an app UID for the duration of the - * test. Has no effect on non-Ravenwood environments. + * @deprecated no longer used. We always use an app UID. */ + @Deprecated public Builder setProcessApp() { - mBuilder.setProcessApp(); return this; } @@ -139,14 +137,10 @@ public final class RavenwoodRule implements TestRule { } /** - * Configure a "main" thread to be available for the duration of the test, as defined - * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. - * - * @deprecated + * @deprecated no longer used. Main thread is always available. */ @Deprecated public Builder setProvideMainThread(boolean provideMainThread) { - mBuilder.setProvideMainThread(provideMainThread); return this; } diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java index d232ef2076be..c85bd23db893 100644 --- a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java +++ b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java @@ -18,6 +18,7 @@ package android.util; import android.util.Log.Level; import com.android.internal.os.RuntimeInit; +import com.android.ravenwood.common.RavenwoodCommonUtils; import java.io.PrintStream; @@ -35,6 +36,9 @@ public class Log_host { } public static int println_native(int bufID, int priority, String tag, String msg) { + if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) { + return msg.length(); // No verbose logging. + } final String buffer; switch (bufID) { case Log.LOG_ID_MAIN: buffer = "main"; break; diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index 4ad7c10a1444..d2c044fdbb5e 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -255,6 +255,11 @@ final class DisplayDeviceInfo { public static final int DIFF_MODE_ID = 1 << 7; /** + * Diff result: The frame rate override list differs. + */ + public static final int DIFF_FRAME_RATE_OVERRIDE = 1 << 8; + + /** * Diff result: Catch-all for "everything changed" */ public static final int DIFF_EVERYTHING = 0XFFFFFFFF; @@ -523,6 +528,9 @@ final class DisplayDeviceInfo { if (modeId != other.modeId) { diff |= DIFF_MODE_ID; } + if (!Arrays.equals(frameRateOverrides, other.frameRateOverrides)) { + diff |= DIFF_FRAME_RATE_OVERRIDE; + } if (!Objects.equals(name, other.name) || !Objects.equals(uniqueId, other.uniqueId) || width != other.width @@ -546,7 +554,6 @@ final class DisplayDeviceInfo { || !Objects.equals(deviceProductInfo, other.deviceProductInfo) || ownerUid != other.ownerUid || !Objects.equals(ownerPackageName, other.ownerPackageName) - || !Arrays.equals(frameRateOverrides, other.frameRateOverrides) || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum) || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum) || !BrightnessSynchronizer.floatEquals(brightnessDefault, diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java index 086f8a94d9b8..5f7bc4effa1b 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java +++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java @@ -27,6 +27,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.utils.DebugUtils; +import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -177,18 +178,22 @@ class DisplayDeviceRepository implements DisplayAdapter.Listener { "handleDisplayDeviceChanged"); } int diff = device.mDebugLastLoggedDeviceInfo.diff(info); - if (diff == DisplayDeviceInfo.DIFF_STATE) { + if (diff == 0) { + Slog.i(TAG, "Display device same: " + info); + } else if (diff == DisplayDeviceInfo.DIFF_STATE) { Slog.i(TAG, "Display device changed state: \"" + info.name + "\", " + Display.stateToString(info.state)); } else if (diff == DisplayDeviceInfo.DIFF_ROTATION) { Slog.i(TAG, "Display device rotated: \"" + info.name + "\", " + Surface.rotationToString(info.rotation)); - } else if (diff - == (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS)) { + } else if ((diff & + (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS + | DisplayDeviceInfo.DIFF_FRAME_RATE_OVERRIDE)) != 0) { Slog.i(TAG, "Display device changed render timings: \"" + info.name + "\", renderFrameRate=" + info.renderFrameRate + ", presentationDeadlineNanos=" + info.presentationDeadlineNanos - + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos); + + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos + + ", frameRateOverrides=" + Arrays.toString(info.frameRateOverrides)); } else if (diff == DisplayDeviceInfo.DIFF_COMMITTED_STATE) { if (DEBUG) { Slog.i(TAG, "Display device changed committed state: \"" + info.name diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 0e77040187e1..5a2610b00772 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -96,6 +96,7 @@ import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayGroupListener; import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener; +import android.hardware.display.DisplayTopology; import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; @@ -118,6 +119,7 @@ import android.os.IBinder.DeathRecipient; import android.os.IThermalService; import android.os.Looper; import android.os.Message; +import android.os.PermissionEnforcer; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -4321,6 +4323,10 @@ public final class DisplayManagerService extends SystemService { @VisibleForTesting final class BinderService extends IDisplayManager.Stub { + BinderService() { + super(PermissionEnforcer.fromContext(getContext())); + } + /** * Returns information about the specified logical display. * @@ -5202,6 +5208,25 @@ public final class DisplayManagerService extends SystemService { } return ddc.getDefaultDozeBrightness(); } + + @EnforcePermission(MANAGE_DISPLAYS) + @Override // Binder call + public DisplayTopology getDisplayTopology() { + getDisplayTopology_enforcePermission(); + if (mDisplayTopologyCoordinator == null) { + return null; + } + return mDisplayTopologyCoordinator.getTopology(); + } + + @EnforcePermission(MANAGE_DISPLAYS) + @Override // Binder call + public void setDisplayTopology(DisplayTopology topology) { + setDisplayTopology_enforcePermission(); + if (mDisplayTopologyCoordinator != null) { + mDisplayTopologyCoordinator.setTopology(topology); + } + } } @VisibleForTesting diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java index b101e5893b97..47226861545f 100644 --- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java +++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.hardware.display.DisplayTopology; import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayInfo; @@ -33,7 +34,7 @@ import java.util.function.BooleanSupplier; class DisplayTopologyCoordinator { @GuardedBy("mLock") - private final DisplayTopology mTopology; + private DisplayTopology mTopology; /** * Check if extended displays are enabled. If not, a topology is not needed. @@ -76,6 +77,21 @@ class DisplayTopologyCoordinator { } /** + * @return A deep copy of the topology. + */ + DisplayTopology getTopology() { + synchronized (mLock) { + return mTopology; + } + } + + void setTopology(DisplayTopology topology) { + synchronized (mLock) { + mTopology = topology; + } + } + + /** * Print the object's state and debug information into the given stream. * @param pw The stream to dump information to. */ @@ -108,6 +124,7 @@ class DisplayTopologyCoordinator { && info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP; } + @VisibleForTesting static class Injector { DisplayTopology getTopology() { return new DisplayTopology(); diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 794eb8754820..0c04be10d06d 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall; import static android.service.dreams.Flags.dreamHandlesBeingObscured; import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; @@ -353,28 +354,32 @@ public final class DreamManagerService extends SystemService { @Override public void onUserStarting(@NonNull TargetUser user) { super.onUserStarting(user); - mHandler.post(() -> { - final int userId = user.getUserIdentifier(); - if (!mPackageMonitors.contains(userId)) { - final PackageMonitor monitor = new PerUserPackageMonitor(); - monitor.register(mContext, UserHandle.of(userId), mHandler); - mPackageMonitors.put(userId, monitor); - } else { - Slog.w(TAG, "Package monitor already registered for " + userId); - } - }); + if (cleanupDreamSettingsOnUninstall()) { + mHandler.post(() -> { + final int userId = user.getUserIdentifier(); + if (!mPackageMonitors.contains(userId)) { + final PackageMonitor monitor = new PerUserPackageMonitor(); + monitor.register(mContext, UserHandle.of(userId), mHandler); + mPackageMonitors.put(userId, monitor); + } else { + Slog.w(TAG, "Package monitor already registered for " + userId); + } + }); + } } @Override public void onUserStopping(@NonNull TargetUser user) { super.onUserStopping(user); - mHandler.post(() -> { - final PackageMonitor monitor = mPackageMonitors.removeReturnOld( - user.getUserIdentifier()); - if (monitor != null) { - monitor.unregister(); - } - }); + if (cleanupDreamSettingsOnUninstall()) { + mHandler.post(() -> { + final PackageMonitor monitor = mPackageMonitors.removeReturnOld( + user.getUserIdentifier()); + if (monitor != null) { + monitor.unregister(); + } + }); + } } private void dumpInternal(PrintWriter pw) { @@ -715,15 +720,23 @@ public final class DreamManagerService extends SystemService { userId)); if (componentNames != null) { // Filter out any components in the removed package. - final ComponentName[] filteredComponents = Arrays.stream(componentNames).filter( - (componentName -> !TextUtils.equals(componentName.getPackageName(), - packageName))).toArray(ComponentName[]::new); + final ComponentName[] filteredComponents = + Arrays.stream(componentNames) + .filter((componentName -> !isSamePackage(packageName, componentName))) + .toArray(ComponentName[]::new); if (filteredComponents.length != componentNames.length) { setDreamComponentsForUser(userId, filteredComponents); } } } + private static boolean isSamePackage(String packageName, ComponentName componentName) { + if (packageName == null || componentName == null) { + return false; + } + return TextUtils.equals(componentName.getPackageName(), packageName); + } + private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) { Settings.Secure.putStringForUser(mContext.getContentResolver(), Settings.Secure.SCREENSAVER_COMPONENTS, @@ -884,7 +897,10 @@ public final class DreamManagerService extends SystemService { } StringBuilder names = new StringBuilder(); for (ComponentName componentName : componentNames) { - if (names.length() > 0) { + if (componentName == null) { + continue; + } + if (!names.isEmpty()) { names.append(','); } names.append(componentName.flattenToString()); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index bf415a344f4c..7505c710f483 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -646,9 +646,9 @@ public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { int address = message.getSource(); int type = message.getParams()[2]; - if (!ActiveSource.of(address, path).equals(getActiveSource())) { - HdmiLogger.debug("Check if a new device is connected to the active path"); - handleNewDeviceAtTheTailOfActivePath(path); + if (getActiveSource().logicalAddress != address && getActivePath() == path) { + HdmiLogger.debug("New logical address detected on the current active path."); + startRoutingControl(path, path, null); } startNewDeviceAction(ActiveSource.of(address, path), type); return Constants.HANDLED; diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 132d6fa377eb..0c5069f81774 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -771,6 +771,14 @@ public class HdmiControlService extends SystemService { Slog.i(TAG, "Device does not support eARC."); } mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController); + if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) { + Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy " + + " mode."); + getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, + HDMI_CEC_CONTROL_ENABLED); + setWasCecDisabledOnStandbyByLowEnergyMode(false); + setCecEnabled(HDMI_CEC_CONTROL_ENABLED); + } if (isCecControlEnabled()) { initializeCec(INITIATED_BY_BOOT_UP); } else { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index acc8f6634f5c..f611c57dab03 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -35,6 +35,7 @@ import android.hardware.contexthub.MessageDeliveryStatus; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubMessage; import android.hardware.location.ContextHubTransaction; +import android.hardware.location.HubInfo; import android.hardware.location.IContextHubCallback; import android.hardware.location.IContextHubClient; import android.hardware.location.IContextHubClientCallback; @@ -57,6 +58,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Pair; import android.util.proto.ProtoOutputStream; @@ -134,6 +136,9 @@ public class ContextHubService extends IContextHubService.Stub { private Map<Integer, ContextHubInfo> mContextHubIdToInfoMap; private List<String> mSupportedContextHubPerms; private List<ContextHubInfo> mContextHubInfoList; + + @Nullable private final HubInfoRegistry mHubInfoRegistry; + private final RemoteCallbackList<IContextHubCallback> mCallbacksList = new RemoteCallbackList<>(); @@ -309,10 +314,21 @@ public class ContextHubService extends IContextHubService.Stub { mContext = context; long startTimeNs = SystemClock.elapsedRealtimeNanos(); mContextHubWrapper = contextHubWrapper; + if (!initContextHubServiceState(startTimeNs)) { Log.e(TAG, "Failed to initialize the Context Hub Service"); + mHubInfoRegistry = null; return; } + + if (Flags.offloadApi()) { + mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper); + Log.i(TAG, "Enabling generic offload API"); + } else { + mHubInfoRegistry = null; + Log.i(TAG, "Disabling generic offload API"); + } + initDefaultClientMap(); initLocationSettingNotifications(); @@ -427,7 +443,7 @@ public class ContextHubService extends IContextHubService.Stub { Pair<List<ContextHubInfo>, List<String>> hubInfo; try { - hubInfo = mContextHubWrapper.getHubs(); + hubInfo = mContextHubWrapper.getContextHubs(); } catch (RemoteException e) { Log.e(TAG, "RemoteException while getting Context Hub info", e); hubInfo = new Pair<>(Collections.emptyList(), Collections.emptyList()); @@ -713,6 +729,16 @@ public class ContextHubService extends IContextHubService.Stub { return mContextHubInfoList; } + @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) + @Override + public List<HubInfo> getHubs() throws RemoteException { + super.getHubs_enforcePermission(); + if (mHubInfoRegistry == null) { + return Collections.emptyList(); + } + return mHubInfoRegistry.getHubs(); + } + /** * Creates an internal load transaction callback to be used for old API clients * @@ -1417,6 +1443,8 @@ public class ContextHubService extends IContextHubService.Stub { } } + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + pw = ipw; pw.println("Dumping ContextHub Service"); pw.println(""); @@ -1428,6 +1456,11 @@ public class ContextHubService extends IContextHubService.Stub { pw.println("Supported permissions: " + Arrays.toString(mSupportedContextHubPerms.toArray())); pw.println(""); + + if (mHubInfoRegistry != null) { + mHubInfoRegistry.dump(ipw); + } + pw.println("=================== NANOAPPS ===================="); // Dump nanoAppHash mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println); diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java new file mode 100644 index 000000000000..68de9dbda2e1 --- /dev/null +++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.contexthub; + +import android.hardware.location.HubInfo; +import android.os.RemoteException; +import android.util.IndentingPrintWriter; +import android.util.Log; + +import java.util.Collections; +import java.util.List; + +class HubInfoRegistry { + private static final String TAG = "HubInfoRegistry"; + + private final IContextHubWrapper mContextHubWrapper; + + private final List<HubInfo> mHubsInfo; + + HubInfoRegistry(IContextHubWrapper contextHubWrapper) { + List<HubInfo> hubInfos; + mContextHubWrapper = contextHubWrapper; + try { + hubInfos = mContextHubWrapper.getHubs(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while getting Hub info", e); + hubInfos = Collections.emptyList(); + } + mHubsInfo = hubInfos; + } + + /** Retrieve the list of hubs available. */ + List<HubInfo> getHubs() { + return mHubsInfo; + } + + void dump(IndentingPrintWriter ipw) { + ipw.println(TAG); + + ipw.increaseIndent(); + for (HubInfo hubInfo : mHubsInfo) { + ipw.println(hubInfo); + } + ipw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index 5e9277ac0faf..6656a6fe9eb4 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -30,9 +30,11 @@ import android.hardware.contexthub.V1_2.HubAppInfo; import android.hardware.contexthub.V1_2.IContexthubCallback; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubTransaction; +import android.hardware.location.HubInfo; import android.hardware.location.NanoAppBinary; import android.hardware.location.NanoAppMessage; import android.hardware.location.NanoAppState; +import android.hardware.location.VendorHubInfo; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -52,13 +54,14 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; /** * @hide */ public abstract class IContextHubWrapper { + private static final boolean DEBUG = false; private static final String TAG = "IContextHubWrapper"; /** @@ -217,10 +220,14 @@ public abstract class IContextHubWrapper { return proxy == null ? null : new ContextHubWrapperAidl(proxy); } - /** - * Calls the appropriate getHubs function depending on the HAL version. - */ - public abstract Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException; + /** Calls the appropriate getHubs function depending on the HAL version. */ + public abstract Pair<List<ContextHubInfo>, List<String>> getContextHubs() + throws RemoteException; + + /** Calls the appropriate getHubs function depending on the HAL version. */ + public List<HubInfo> getHubs() throws RemoteException { + return Collections.emptyList(); + } /** * @return True if this version of the Contexthub HAL supports Location setting notifications. @@ -556,7 +563,7 @@ public abstract class IContextHubWrapper { mIsTestModeEnabled.set(false); } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { android.hardware.contexthub.IContextHub hub = getHub(); if (hub == null) { return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(), @@ -574,6 +581,47 @@ public abstract class IContextHubWrapper { return new Pair(hubInfoList, new ArrayList<String>(supportedPermissions)); } + public List<HubInfo> getHubs() throws RemoteException { + android.hardware.contexthub.IContextHub hub = getHub(); + if (hub == null) { + return Collections.emptyList(); + } + + List<HubInfo> retVal = new ArrayList<>(); + final List<android.hardware.contexthub.HubInfo> halHubs = hub.getHubs(); + + for (android.hardware.contexthub.HubInfo halHub : halHubs) { + /* HAL -> API Type conversion */ + final HubInfo hubInfo; + switch (halHub.hubDetails.getTag()) { + case android.hardware.contexthub.HubInfo.HubDetails.contextHubInfo: + ContextHubInfo contextHubInfo = + new ContextHubInfo(halHub.hubDetails.getContextHubInfo()); + hubInfo = new HubInfo(halHub.hubId, contextHubInfo); + break; + case android.hardware.contexthub.HubInfo.HubDetails.vendorHubInfo: + VendorHubInfo vendorHubInfo = + new VendorHubInfo(halHub.hubDetails.getVendorHubInfo()); + hubInfo = new HubInfo(halHub.hubId, vendorHubInfo); + break; + default: + Log.w(TAG, "getHubs: invalid hub: " + halHub); + // Invalid + continue; + } + + if (DEBUG) { + Log.i(TAG, "getHubs: hubInfo=" + hubInfo); + } + retVal.add(hubInfo); + } + + if (DEBUG) { + Log.i(TAG, "getHubs: total count=" + retVal.size()); + } + return retVal; + } + public boolean supportsLocationSettingNotifications() { return true; } @@ -1061,7 +1109,7 @@ public abstract class IContextHubWrapper { mHub = hub; } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>(); for (ContextHub hub : mHub.getHubs()) { hubInfoList.add(new ContextHubInfo(hub)); @@ -1106,7 +1154,7 @@ public abstract class IContextHubWrapper { mHub = hub; } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>(); for (ContextHub hub : mHub.getHubs()) { hubInfoList.add(new ContextHubInfo(hub)); @@ -1170,7 +1218,7 @@ public abstract class IContextHubWrapper { mHubInfo = new Pair(hubInfoList, supportedPermissions); } - public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException { + public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException { mHub.getHubs_1_2(this); return mHubInfo; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 73ae51c6e64a..14be59f27f84 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -254,6 +254,7 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.END_TAG; import static org.xmlpull.v1.XmlPullParser.START_TAG; +import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -10308,6 +10309,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (pictureInPictureArgs != null && pictureInPictureArgs.hasSourceBoundsHint()) { pictureInPictureArgs.getSourceRectHint().offset(windowBounds.left, windowBounds.top); } + + if (android.app.Flags.enableTvImplicitEnterPipRestriction()) { + PackageManager pm = mAtmService.mContext.getPackageManager(); + if (pictureInPictureArgs.isAutoEnterEnabled() + && pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) + && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, packageName) + == PackageManager.PERMISSION_DENIED) { + Log.i(TAG, + "Auto-enter PiP only allowed on TV if android.permission" + + ".TV_IMPLICIT_ENTER_PIP permission is held by the app."); + PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); + builder.setAutoEnterEnabled(false); + pictureInPictureArgs.copyOnlySet(builder.build()); + } + } } private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) { diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index 37a34eeb9724..205ff058275a 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -29,7 +29,6 @@ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.MANAGE_USB" /> - <uses-permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE" /> <!-- Permissions needed for DisplayTransformManagerTest --> <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /> diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index c741cae1c135..80e5ee39c13d 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -19,13 +19,16 @@ package com.android.server.display; import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; import static android.Manifest.permission.ADD_TRUSTED_DISPLAY; import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT; +import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS; import static android.Manifest.permission.MANAGE_DISPLAYS; +import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; +import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; @@ -96,6 +99,7 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerInternal; import android.hardware.display.DisplayManagerInternal.DisplayOffloader; +import android.hardware.display.DisplayTopology; import android.hardware.display.DisplayViewport; import android.hardware.display.DisplayedContentSample; import android.hardware.display.DisplayedContentSamplingAttributes; @@ -111,11 +115,13 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.MessageQueue; +import android.os.PermissionEnforcer; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserManager; +import android.os.test.FakePermissionEnforcer; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; @@ -251,6 +257,8 @@ public class DisplayManagerServiceTest { private int[] mAllowedHdrOutputTypes; + private final FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer(); + private final DisplayManagerService.Injector mShortMockedInjector = new DisplayManagerService.Injector() { @Override @@ -428,6 +436,13 @@ public class DisplayManagerServiceTest { when(mContext.getResources()).thenReturn(mResources); mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class)); + mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS); + mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE); + doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName( + eq(PermissionEnforcer.class)); + doReturn(mPermissionEnforcer).when(mContext).getSystemService( + eq(Context.PERMISSION_ENFORCER_SERVICE)); + VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext); when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm); when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); @@ -3667,6 +3682,87 @@ public class DisplayManagerServiceTest { verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder, callingUid); } + @Test + public void testGetDisplayTopology() { + Settings.Global.putInt(mContext.getContentResolver(), + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1); + manageDisplaysPermission(/* granted= */ true); + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + DisplayTopology topology = displayManagerBinderService.getDisplayTopology(); + assertNotNull(topology); + DisplayTopology.TreeNode display = topology.getRoot(); + assertNotNull(display); + assertEquals(Display.DEFAULT_DISPLAY, display.getDisplayId()); + } + + @Test + public void testGetDisplayTopology_NullIfFlagDisabled() { + manageDisplaysPermission(/* granted= */ true); + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + DisplayTopology topology = displayManagerBinderService.getDisplayTopology(); + assertNull(topology); + } + + @Test + public void testGetDisplayTopology_withoutPermission_shouldThrowException() { + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + assertThrows(SecurityException.class, displayManagerBinderService::getDisplayTopology); + } + + @Test + public void testSetDisplayTopology() { + manageDisplaysPermission(/* granted= */ true); + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + displayManagerBinderService.setDisplayTopology(new DisplayTopology()); + } + + @Test + public void testSetDisplayTopology_withoutPermission_shouldThrowException() { + when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true); + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + DisplayManagerInternal localService = displayManager.new LocalService(); + DisplayManagerService.BinderService displayManagerBinderService = + displayManager.new BinderService(); + registerDefaultDisplays(displayManager); + initDisplayPowerController(localService); + + assertThrows(SecurityException.class, + () -> displayManagerBinderService.setDisplayTopology(new DisplayTopology())); + } + private void initDisplayPowerController(DisplayManagerInternal localService) { localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() { @Override @@ -3850,6 +3946,10 @@ public class DisplayManagerServiceTest { DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked()); displayDeviceInfo.modeId = modeId; + if (modeId > 0 && modeId <= displayDeviceInfo.supportedModes.length) { + displayDeviceInfo.renderFrameRate = + displayDeviceInfo.supportedModes[modeId - 1].getRefreshRate(); + } updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo); } @@ -4017,9 +4117,11 @@ public class DisplayManagerServiceTest { private void manageDisplaysPermission(boolean granted) { if (granted) { doNothing().when(mContext).enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any()); + mPermissionEnforcer.grant(MANAGE_DISPLAYS); } else { doThrow(new SecurityException("MANAGE_DISPLAYS permission denied")).when(mContext) .enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any()); + mPermissionEnforcer.revoke(MANAGE_DISPLAYS); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt index 85e73561cf59..a2d2a81b20b4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt @@ -16,6 +16,7 @@ package com.android.server.display +import android.hardware.display.DisplayTopology import android.util.DisplayMetrics import android.view.Display import android.view.DisplayInfo diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt deleted file mode 100644 index cd8c26d0d337..000000000000 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt +++ /dev/null @@ -1,476 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.display - -import android.view.Display -import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM -import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP -import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT -import com.google.common.truth.Truth.assertThat -import org.junit.Test - -class DisplayTopologyTest { - private val topology = DisplayTopology() - - @Test - fun addOneDisplay() { - val displayId = 1 - val width = 800f - val height = 600f - - topology.addDisplay(displayId, width, height) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId) - - val display = topology.mRoot!! - assertThat(display.mDisplayId).isEqualTo(displayId) - assertThat(display.mWidth).isEqualTo(width) - assertThat(display.mHeight).isEqualTo(height) - assertThat(display.mChildren).isEmpty() - } - - @Test - fun addTwoDisplays() { - val displayId1 = 1 - val width1 = 800f - val height1 = 600f - - val displayId2 = 2 - val width2 = 1000f - val height2 = 1500f - - topology.addDisplay(displayId1, width1, height1) - topology.addDisplay(displayId2, width2, height2) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - val display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - val display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).isEmpty() - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - } - - @Test - fun addManyDisplays() { - val displayId1 = 1 - val width1 = 800f - val height1 = 600f - - val displayId2 = 2 - val width2 = 1000f - val height2 = 1500f - - topology.addDisplay(displayId1, width1, height1) - topology.addDisplay(displayId2, width2, height2) - - val noOfDisplays = 30 - for (i in 3..noOfDisplays) { - topology.addDisplay(/* displayId= */ i, width1, height1) - } - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - val display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - val display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).hasSize(1) - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - - var display = display2 - for (i in 3..noOfDisplays) { - display = display.mChildren[0] - assertThat(display.mDisplayId).isEqualTo(i) - assertThat(display.mWidth).isEqualTo(width1) - assertThat(display.mHeight).isEqualTo(height1) - // The last display should have no children - assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) - assertThat(display.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(display.mOffset).isEqualTo(0) - } - } - - @Test - fun removeDisplays() { - val displayId1 = 1 - val width1 = 800f - val height1 = 600f - - val displayId2 = 2 - val width2 = 1000f - val height2 = 1500f - - topology.addDisplay(displayId1, width1, height1) - topology.addDisplay(displayId2, width2, height2) - - val noOfDisplays = 30 - for (i in 3..noOfDisplays) { - topology.addDisplay(/* displayId= */ i, width1, height1) - } - - var removedDisplays = arrayOf(20) - topology.removeDisplay(20) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - var display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - var display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).hasSize(1) - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - - var display = display2 - for (i in 3..noOfDisplays) { - if (i in removedDisplays) { - continue - } - display = display.mChildren[0] - assertThat(display.mDisplayId).isEqualTo(i) - assertThat(display.mWidth).isEqualTo(width1) - assertThat(display.mHeight).isEqualTo(height1) - // The last display should have no children - assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) - assertThat(display.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(display.mOffset).isEqualTo(0) - } - - topology.removeDisplay(22) - removedDisplays += 22 - topology.removeDisplay(23) - removedDisplays += 23 - topology.removeDisplay(25) - removedDisplays += 25 - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - - display1 = topology.mRoot!! - assertThat(display1.mDisplayId).isEqualTo(displayId1) - assertThat(display1.mWidth).isEqualTo(width1) - assertThat(display1.mHeight).isEqualTo(height1) - assertThat(display1.mChildren).hasSize(1) - - display2 = display1.mChildren[0] - assertThat(display2.mDisplayId).isEqualTo(displayId2) - assertThat(display2.mWidth).isEqualTo(width2) - assertThat(display2.mHeight).isEqualTo(height2) - assertThat(display2.mChildren).hasSize(1) - assertThat(display2.mPosition).isEqualTo(POSITION_TOP) - assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2) - - display = display2 - for (i in 3..noOfDisplays) { - if (i in removedDisplays) { - continue - } - display = display.mChildren[0] - assertThat(display.mDisplayId).isEqualTo(i) - assertThat(display.mWidth).isEqualTo(width1) - assertThat(display.mHeight).isEqualTo(height1) - // The last display should have no children - assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0) - assertThat(display.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(display.mOffset).isEqualTo(0) - } - } - - @Test - fun removeAllDisplays() { - val displayId = 1 - val width = 800f - val height = 600f - - topology.addDisplay(displayId, width, height) - topology.removeDisplay(displayId) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(Display.INVALID_DISPLAY) - assertThat(topology.mRoot).isNull() - } - - @Test - fun removeDisplayThatDoesNotExist() { - val displayId = 1 - val width = 800f - val height = 600f - - topology.addDisplay(displayId, width, height) - topology.removeDisplay(3) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId) - - val display = topology.mRoot!! - assertThat(display.mDisplayId).isEqualTo(displayId) - assertThat(display.mWidth).isEqualTo(width) - assertThat(display.mHeight).isEqualTo(height) - assertThat(display.mChildren).isEmpty() - } - - @Test - fun removePrimaryDisplay() { - val displayId1 = 1 - val displayId2 = 2 - val width = 800f - val height = 600f - - topology.addDisplay(displayId1, width, height) - topology.addDisplay(displayId2, width, height) - topology.mPrimaryDisplayId = displayId2 - topology.removeDisplay(displayId2) - - assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1) - val display = topology.mRoot!! - assertThat(display.mDisplayId).isEqualTo(displayId1) - assertThat(display.mWidth).isEqualTo(width) - assertThat(display.mHeight).isEqualTo(height) - assertThat(display.mChildren).isEmpty() - } - - @Test - fun normalization_noOverlaps_leavesTopologyUnchanged() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 600f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display2.mChildren.add(display4) - - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(600f) - assertThat(actualDisplay1.mChildren).hasSize(2) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(600f) - assertThat(actualDisplay2.mHeight).isEqualTo(200f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(1) - - val actualDisplay3 = actualDisplay1.mChildren[1] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay3.mOffset).isEqualTo(400f) - assertThat(actualDisplay3.mChildren).isEmpty() - - val actualDisplay4 = actualDisplay2.mChildren[0] - assertThat(actualDisplay4.mDisplayId).isEqualTo(4) - assertThat(actualDisplay4.mWidth).isEqualTo(200f) - assertThat(actualDisplay4.mHeight).isEqualTo(600f) - assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay4.mOffset).isEqualTo(0f) - assertThat(actualDisplay4.mChildren).isEmpty() - } - - @Test - fun normalization_moveDisplayWithoutReparenting() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 600f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display2.mChildren.add(display4) - - // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent. - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(600f) - assertThat(actualDisplay1.mChildren).hasSize(1) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(200f) - assertThat(actualDisplay2.mHeight).isEqualTo(600f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(2) - - val actualDisplay3 = actualDisplay2.mChildren[1] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay3.mOffset).isEqualTo(10f) - assertThat(actualDisplay3.mChildren).isEmpty() - - val actualDisplay4 = actualDisplay2.mChildren[0] - assertThat(actualDisplay4.mDisplayId).isEqualTo(4) - assertThat(actualDisplay4.mWidth).isEqualTo(200f) - assertThat(actualDisplay4.mHeight).isEqualTo(600f) - assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay4.mOffset).isEqualTo(210f) - assertThat(actualDisplay4.mChildren).isEmpty() - } - - @Test - fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 50f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - // Display 3 gets moved and its left side is still on the same line as the right side - // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2 - // becomes its new parent. - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(50f) - assertThat(actualDisplay1.mChildren).hasSize(1) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(600f) - assertThat(actualDisplay2.mHeight).isEqualTo(200f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(1) - - val actualDisplay3 = actualDisplay2.mChildren[0] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_BOTTOM) - assertThat(actualDisplay3.mOffset).isEqualTo(0f) - assertThat(actualDisplay3.mChildren).isEmpty() - } - - @Test - fun normalization_moveAndReparentDisplay() { - val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f, - /* height= */ 600f, /* position= */ null, /* offset= */ 0f) - topology.mRoot = display1 - - val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display1.mChildren.add(display2) - - val primaryDisplayId = 3 - val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f, - /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f) - display1.mChildren.add(display3) - topology.mPrimaryDisplayId = primaryDisplayId - - val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f, - /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f) - display2.mChildren.add(display4) - - topology.normalize() - - assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId) - - val actualDisplay1 = topology.mRoot!! - assertThat(actualDisplay1.mDisplayId).isEqualTo(1) - assertThat(actualDisplay1.mWidth).isEqualTo(200f) - assertThat(actualDisplay1.mHeight).isEqualTo(600f) - assertThat(actualDisplay1.mChildren).hasSize(1) - - val actualDisplay2 = actualDisplay1.mChildren[0] - assertThat(actualDisplay2.mDisplayId).isEqualTo(2) - assertThat(actualDisplay2.mWidth).isEqualTo(200f) - assertThat(actualDisplay2.mHeight).isEqualTo(600f) - assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay2.mOffset).isEqualTo(0f) - assertThat(actualDisplay2.mChildren).hasSize(1) - - val actualDisplay3 = actualDisplay2.mChildren[0] - assertThat(actualDisplay3.mDisplayId).isEqualTo(3) - assertThat(actualDisplay3.mWidth).isEqualTo(600f) - assertThat(actualDisplay3.mHeight).isEqualTo(200f) - assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay3.mOffset).isEqualTo(400f) - assertThat(actualDisplay3.mChildren).hasSize(1) - - val actualDisplay4 = actualDisplay3.mChildren[0] - assertThat(actualDisplay4.mDisplayId).isEqualTo(4) - assertThat(actualDisplay4.mWidth).isEqualTo(200f) - assertThat(actualDisplay4.mHeight).isEqualTo(600f) - assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT) - assertThat(actualDisplay4.mOffset).isEqualTo(-400f) - assertThat(actualDisplay4.mChildren).isEmpty() - } -}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java index 685e8d6a3bc5..e611867493eb 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java @@ -65,7 +65,7 @@ public class ContextHubServiceTest { new Pair<>(Arrays.asList(mMockContextHubInfo), Arrays.asList("")); when(mMockContextHubInfo.getId()).thenReturn(CONTEXT_HUB_ID); when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING); - when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo); + when(mMockContextHubWrapper.getContextHubs()).thenReturn(hubInfo); when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true); when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true); 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 0b89c11a11f4..38ff3a22022d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -2291,7 +2291,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) public void testMoveAggregateGroups_updateChannel_multipleChannels_regroupOnClassifEnabled() { final String pkg = "package"; final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg, @@ -2366,7 +2368,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) public void testMoveSections_notificationBundled() { final List<NotificationRecord> notificationList = new ArrayList<>(); final String pkg = "package"; @@ -2436,7 +2440,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) public void testCacheAndCancelAppSummary_notificationBundled() { // check that the original app summary is canceled & cached on classification regrouping final List<NotificationRecord> notificationList = new ArrayList<>(); @@ -2495,6 +2501,7 @@ public class GroupHelperTest extends UiServiceTestCase { @Test @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS}) public void testSingletonGroupsRegrouped_notificationBundledBeforeDelayTimeout() { @@ -2569,6 +2576,7 @@ public class GroupHelperTest extends UiServiceTestCase { @Test @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION, FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS}) public void testSingletonGroupsRegrouped_notificationBundledAfterDelayTimeout() { diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java index a34094ce6452..98949d0c45cf 100644 --- a/telecomm/java/android/telecom/Log.java +++ b/telecomm/java/android/telecom/Log.java @@ -68,7 +68,7 @@ public class Log { // Used to synchronize singleton logging lazy initialization private static final Object sSingletonSync = new Object(); private static EventManager sEventManager; - private static SessionManager sSessionManager; + private static volatile SessionManager sSessionManager; private static Object sLock = null; /** @@ -379,6 +379,23 @@ public class Log { return sSessionManager; } + @VisibleForTesting + public static SessionManager setSessionManager(Context context, + java.lang.Runnable cleanSessionRunnable) { + // Checking for null again outside of synchronization because we only need to synchronize + // during the lazy loading of the session logger. We don't need to synchronize elsewhere. + if (sSessionManager == null) { + synchronized (sSingletonSync) { + if (sSessionManager == null) { + sSessionManager = new SessionManager(cleanSessionRunnable); + sSessionManager.setContext(context); + return sSessionManager; + } + } + } + return sSessionManager; + } + public static void setTag(String tag) { TAG = tag; DEBUG = isLoggable(android.util.Log.DEBUG); diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java index 00e344c67cc5..ac1e69e92ec0 100644 --- a/telecomm/java/android/telecom/Logging/SessionManager.java +++ b/telecomm/java/android/telecom/Logging/SessionManager.java @@ -62,9 +62,7 @@ public class SessionManager { @VisibleForTesting public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64); - @VisibleForTesting - public java.lang.Runnable mCleanStaleSessions = () -> - cleanupStaleSessions(getSessionCleanupTimeoutMs()); + private final java.lang.Runnable mCleanStaleSessions; private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper()); // Overridden in LogTest to skip query to ContentProvider @@ -110,29 +108,39 @@ public class SessionManager { } public SessionManager() { + mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs()); + } + + @VisibleForTesting + public SessionManager(java.lang.Runnable cleanStaleSessionsRunnable) { + mCleanStaleSessions = cleanStaleSessionsRunnable; } private long getSessionCleanupTimeoutMs() { return mSessionCleanupTimeoutMs.get(); } - private synchronized void resetStaleSessionTimer() { + private void resetStaleSessionTimer() { if (!Flags.endSessionImprovements()) { - mSessionCleanupHandler.removeCallbacksAndMessages(null); - // Will be null in Log Testing - if (mCleanStaleSessions != null) { - mSessionCleanupHandler.postDelayed(mCleanStaleSessions, - getSessionCleanupTimeoutMs()); - } - } else { - if (mCleanStaleSessions != null - && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) { + resetStaleSessionTimerOld(); + return; + } + // Will be null in Log Testing + if (mCleanStaleSessions == null) return; + synchronized (mSessionCleanupHandler) { + if (!mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) { mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); } } } + private synchronized void resetStaleSessionTimerOld() { + if (mCleanStaleSessions == null) return; + mSessionCleanupHandler.removeCallbacksAndMessages(null); + mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs()); + } + /** * Determines whether or not to start a new session or continue an existing session based on * the {@link Session.Info} info passed into startSession. If info is null, a new Session is |