summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp17
-rw-r--r--Android.bp2
-rw-r--r--core/api/current.txt180
-rw-r--r--core/java/android/app/Activity.java22
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java4
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java2
-rw-r--r--core/java/android/app/multitasking.aconfig8
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java33
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java32
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java115
-rw-r--r--core/java/android/hardware/display/DisplayManager.java24
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java26
-rw-r--r--core/java/android/hardware/display/DisplayTopology.aidl19
-rw-r--r--core/java/android/hardware/display/DisplayTopology.java (renamed from services/core/java/com/android/server/display/DisplayTopology.java)229
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl10
-rw-r--r--core/java/android/hardware/location/ContextHubInfo.java1
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java38
-rw-r--r--core/java/android/hardware/location/HubInfo.aidl20
-rw-r--r--core/java/android/hardware/location/HubInfo.java153
-rw-r--r--core/java/android/hardware/location/IContextHubService.aidl5
-rw-r--r--core/java/android/hardware/location/VendorHubInfo.aidl20
-rw-r--r--core/java/android/hardware/location/VendorHubInfo.java95
-rw-r--r--core/java/android/service/dreams/flags.aconfig10
-rw-r--r--core/java/android/service/settings/preferences/GetValueRequest.aidl4
-rw-r--r--core/java/android/service/settings/preferences/GetValueRequest.java139
-rw-r--r--core/java/android/service/settings/preferences/GetValueResult.aidl4
-rw-r--r--core/java/android/service/settings/preferences/GetValueResult.java213
-rw-r--r--core/java/android/service/settings/preferences/IGetValueCallback.aidl9
-rw-r--r--core/java/android/service/settings/preferences/IMetadataCallback.aidl9
-rw-r--r--core/java/android/service/settings/preferences/ISetValueCallback.aidl9
-rw-r--r--core/java/android/service/settings/preferences/MetadataRequest.aidl4
-rw-r--r--core/java/android/service/settings/preferences/MetadataRequest.java75
-rw-r--r--core/java/android/service/settings/preferences/MetadataResult.aidl4
-rw-r--r--core/java/android/service/settings/preferences/MetadataResult.java164
-rw-r--r--core/java/android/service/settings/preferences/SetValueRequest.aidl4
-rw-r--r--core/java/android/service/settings/preferences/SetValueRequest.java158
-rw-r--r--core/java/android/service/settings/preferences/SetValueResult.aidl4
-rw-r--r--core/java/android/service/settings/preferences/SetValueResult.java179
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java436
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceValue.java220
-rw-r--r--core/java/android/view/DisplayInfo.java4
-rw-r--r--core/res/Android.bp2
-rw-r--r--core/res/AndroidManifest.xml33
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt472
-rw-r--r--core/tests/coretests/src/android/view/DisplayInfoTest.java17
-rw-r--r--graphics/java/android/graphics/ColorSpace.java2
-rw-r--r--keystore/java/android/security/keystore/KeyStoreManager.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt12
-rw-r--r--libs/appfunctions/Android.bp8
-rw-r--r--libs/appfunctions/api/current.txt18
-rw-r--r--libs/appfunctions/appfunctions.extension.xml (renamed from libs/appfunctions/appfunctions.sidecar.xml)4
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java)2
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java)3
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java)6
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java)4
-rw-r--r--libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java (renamed from libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java)35
-rw-r--r--libs/appfunctions/tests/Android.bp2
-rw-r--r--libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt (renamed from libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt)15
-rw-r--r--media/java/android/media/MediaFormat.java12
-rw-r--r--media/java/android/media/audio/common/AidlConversion.java4
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig2
-rw-r--r--media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java22
-rw-r--r--packages/SettingsLib/Android.bp16
-rw-r--r--packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java2
-rw-r--r--packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java2
-rw-r--r--packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt2
-rw-r--r--packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt55
-rw-r--r--packages/SystemUI/res/values/config.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt15
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java30
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java16
-rw-r--r--ravenwood/runtime-helper-src/framework/android/util/Log_host.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceInfo.java9
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceRepository.java13
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java25
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java19
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java58
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java6
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java8
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java35
-rw-r--r--services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java60
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java66
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java16
-rw-r--r--services/tests/displayservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java102
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt476
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java14
-rw-r--r--telecomm/java/android/telecom/Log.java19
-rw-r--r--telecomm/java/android/telecom/Logging/SessionManager.java34
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