summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp8
-rw-r--r--apex/jobscheduler/framework/aconfig/job.aconfig2
-rw-r--r--core/api/system-current.txt9
-rw-r--r--core/java/android/app/ApplicationPackageManager.java2
-rw-r--r--core/java/android/app/activity_manager.aconfig1
-rw-r--r--core/java/android/app/admin/flags/flags.aconfig2
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig1
-rw-r--r--core/java/android/app/jank/flags.aconfig1
-rw-r--r--core/java/android/appwidget/flags.aconfig1
-rw-r--r--core/java/android/companion/flags.aconfig1
-rw-r--r--core/java/android/content/pm/flags.aconfig2
-rw-r--r--core/java/android/content/pm/multiuser.aconfig1
-rw-r--r--core/java/android/content/pm/xr.aconfig1
-rw-r--r--core/java/android/hardware/biometrics/flags.aconfig1
-rw-r--r--core/java/android/hardware/contexthub/HubEndpointSession.java3
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpoint.aidl6
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig1
-rw-r--r--core/java/android/hardware/soundtrigger/ConversionUtil.java4
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java42
-rw-r--r--core/java/android/hardware/usb/flags/usb_framework_flags.aconfig1
-rw-r--r--core/java/android/net/flags.aconfig1
-rw-r--r--core/java/android/os/flags.aconfig2
-rw-r--r--core/java/android/permission/flags.aconfig2
-rw-r--r--core/java/android/security/flags.aconfig3
-rw-r--r--core/java/android/security/responsible_apis_flags.aconfig1
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java1
-rw-r--r--core/java/android/service/quickaccesswallet/flags.aconfig1
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java2
-rw-r--r--core/java/android/text/flags/flags.aconfig4
-rw-r--r--core/java/android/view/ViewRootImpl.java9
-rw-r--r--core/java/android/view/WindowManagerImpl.java2
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig17
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java4
-rw-r--r--core/java/android/view/autofill/AutofillManager.java3
-rw-r--r--core/java/android/view/flags/refresh_rate_flags.aconfig1
-rw-r--r--core/java/android/view/flags/view_flags.aconfig2
-rw-r--r--core/java/android/view/inputmethod/flags.aconfig2
-rw-r--r--core/java/android/window/DesktopModeFlags.java13
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig1
-rw-r--r--core/java/android/window/flags/responsible_apis.aconfig3
-rw-r--r--core/java/android/window/flags/wallpaper_manager.aconfig1
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig4
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig1
-rw-r--r--core/java/com/android/internal/pm/pkg/component/AconfigFlags.java12
-rw-r--r--core/res/res/values/config.xml10
-rw-r--r--core/res/res/values/config_telephony.xml6
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/Android.bp21
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt93
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt78
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt76
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt43
-rw-r--r--libs/hwui/aconfig/hwui_flags.aconfig3
-rw-r--r--location/api/system-current.txt8
-rw-r--r--location/java/android/location/flags/location.aconfig6
-rw-r--r--location/java/android/location/provider/IPopulationDensityProvider.aidl45
-rw-r--r--location/java/android/location/provider/IS2CellIdsCallback.aidl36
-rw-r--r--location/java/android/location/provider/IS2LevelCallback.aidl34
-rw-r--r--location/java/android/location/provider/PopulationDensityProviderBase.java192
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java199
-rw-r--r--media/java/android/media/flags/projection.aconfig14
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerDetector.java2
-rw-r--r--native/android/OWNERS2
-rw-r--r--native/android/libandroid.map.txt1
-rw-r--r--native/android/surface_control.cpp22
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml3
-rw-r--r--packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt1
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig2
-rw-r--r--packages/SettingsProvider/res/xml/bookmarks.xml62
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java130
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt12
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt12
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt41
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java4
-rw-r--r--packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt2
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt75
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt34
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt127
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt31
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt32
-rw-r--r--packages/SystemUI/res/values/strings.xml17
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt176
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt)15
-rw-r--r--services/accessibility/accessibility.aconfig15
-rw-r--r--services/autofill/bugfixes.aconfig10
-rw-r--r--services/autofill/features.aconfig1
-rw-r--r--services/backup/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig6
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java37
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudger.java63
-rw-r--r--services/core/java/com/android/server/location/fudger/LocationFudgerCache.java199
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java14
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java119
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java5
-rw-r--r--services/core/java/com/android/server/pm/dex/ArtManagerService.java3
-rw-r--r--services/core/java/com/android/server/power/stats/flags.aconfig1
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java41
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java6
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java98
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java65
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCoordinator.java22
-rw-r--r--services/core/java/com/android/server/wm/DisplayWindowSettings.java2
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java6
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java43
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java32
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java6
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java37
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java327
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java72
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java70
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java55
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java42
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java4
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java2
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java30
-rw-r--r--telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl6
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt41
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml21
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml6
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java15
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java71
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java31
161 files changed, 3303 insertions, 767 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f1906b574bb0..4302d24b3a72 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -23,6 +23,7 @@ aconfig_declarations_group {
"aconfig_mediacodec_flags_java_lib",
"aconfig_settingslib_flags_java_lib",
"aconfig_trade_in_mode_flags_java_lib",
+ "adpf_flags_java_lib",
"android.adaptiveauth.flags-aconfig-java",
"android.app.appfunctions.flags-aconfig-java",
"android.app.assist.flags-aconfig-java",
@@ -874,6 +875,13 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Adaptive Performance
+java_aconfig_library {
+ name: "adpf_flags_java_lib",
+ aconfig_declarations: "adpf_flags",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Graphics
java_aconfig_library {
name: "hwui_flags_java_lib",
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 47a85498f51b..63624d8cad4a 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -29,6 +29,7 @@ flag {
namespace: "backstage_power"
description: "Detect, report and take action on jobs that maybe abandoned by the app without calling jobFinished."
bug: "372529068"
+ is_exported: true
}
flag {
@@ -36,6 +37,7 @@ flag {
namespace: "backstage_power"
description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
bug: "374175032"
+ is_exported: true
}
flag {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 52d13d1a926d..612a48ac00a7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5254,9 +5254,9 @@ package android.hardware.contexthub {
}
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSession implements java.lang.AutoCloseable {
- method @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void close();
+ method public void close();
method @Nullable public android.hardware.contexthub.HubServiceInfo getServiceInfo();
- method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
+ method @NonNull public android.hardware.location.ContextHubTransaction<java.lang.Void> sendMessage(@NonNull android.hardware.contexthub.HubMessage);
}
@FlaggedApi("android.chre.flags.offload_api") public class HubEndpointSessionResult {
@@ -7192,8 +7192,8 @@ package android.hardware.soundtrigger {
method public int getAudioCapabilities();
method @NonNull public byte[] getData();
method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra> getKeyphrases();
- method public boolean isAllowMultipleTriggers();
method public boolean isCaptureRequested();
+ method public boolean isMultipleTriggersAllowed();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
}
@@ -7201,11 +7201,11 @@ package android.hardware.soundtrigger {
public static final class SoundTrigger.RecognitionConfig.Builder {
ctor public SoundTrigger.RecognitionConfig.Builder();
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig build();
- method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setMultipleTriggersAllowed(boolean);
}
public static class SoundTrigger.RecognitionEvent {
@@ -16271,6 +16271,7 @@ package android.telephony {
method @FlaggedApi("android.permission.flags.get_emergency_role_holder_api_enabled") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getEmergencyAssistancePackageName();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
+ method @FlaggedApi("com.android.internal.telephony.flags.get_group_id_level2") @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getGroupIdLevel2();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<java.lang.String> getImsPcscfAddresses();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @Nullable @RequiresPermission(android.Manifest.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER) public String getImsPrivateUserIdentity();
method @FlaggedApi("com.android.internal.telephony.flags.support_isim_record") @NonNull @RequiresPermission(value=android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, conditional=true) public java.util.List<android.net.Uri> getImsPublicUserIdentities();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 3cbea87e135e..da338474a448 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -798,7 +798,7 @@ public class ApplicationPackageManager extends PackageManager {
private final static PropertyInvalidatedCache<HasSystemFeatureQuery, Boolean>
mHasSystemFeatureCache = new PropertyInvalidatedCache<>(
new PropertyInvalidatedCache.Args(MODULE_SYSTEM)
- .api(HAS_SYSTEM_FEATURE_API).maxEntries(256).isolateUids(false),
+ .api(HAS_SYSTEM_FEATURE_API).maxEntries(SDK_FEATURE_COUNT).isolateUids(false),
HAS_SYSTEM_FEATURE_API, null) {
@Override
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 1f31ab5d1849..44940aee6a49 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -164,4 +164,5 @@ flag {
name: "app_start_info_component"
description: "Control ApplicationStartInfo component field and API"
bug: "362537357"
+ is_exported: true
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 581efa5d2efa..1d5f06c45153 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -367,6 +367,7 @@ flag {
namespace: "enterprise"
description: "API that removes a given managed profile."
bug: "372652841"
+ is_exported: true
}
flag {
@@ -390,6 +391,7 @@ flag {
namespace: "enterprise"
description: "Split up existing create and provision managed profile API."
bug: "375382324"
+ is_exported: true
}
flag {
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 5e09517d1edc..529b59ac424d 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "machine_learning"
description: "Flag to enable the service"
bug: "309689654"
+ is_exported: true
}
flag {
name: "enable_token_refresh"
diff --git a/core/java/android/app/jank/flags.aconfig b/core/java/android/app/jank/flags.aconfig
index 5657f7ee5194..a62df1b3cbf7 100644
--- a/core/java/android/app/jank/flags.aconfig
+++ b/core/java/android/app/jank/flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "system_performance"
description: "Control the API portion of Detailed Application Jank Metrics"
bug: "366264614"
+ is_exported: true
}
flag {
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 17bcdb013673..9914ba2b020a 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -111,4 +111,5 @@ flag {
namespace: "app_widgets"
description: "Enable collection of widget engagement metrics"
bug: "364655296"
+ is_exported: true
}
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 2539a12a2a14..2b9f70037279 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -46,6 +46,7 @@ flag {
namespace: "companion"
description: "Unpair with an associated bluetooth device"
bug: "322237619"
+ is_exported: true
}
flag {
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index fbe581c5d524..8ba2dcc2a7cf 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -341,6 +341,7 @@ flag {
description: "Feature flag to introduce a new way to change the launcher badging."
bug: "364760703"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -357,6 +358,7 @@ flag {
namespace: "package_manager_service"
description: "Block app installations that specify an incompatible minor SDK version"
bug: "377302905"
+ is_exported: true
}
flag {
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index d351ebc22026..f29e2e8f10a3 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -593,4 +593,5 @@ flag {
namespace: "profile_experiences"
description: "Add support for LauncherUserInfo configs"
bug: "346553745"
+ is_exported: true
}
diff --git a/core/java/android/content/pm/xr.aconfig b/core/java/android/content/pm/xr.aconfig
index 61835c162c49..a26f48e30c7e 100644
--- a/core/java/android/content/pm/xr.aconfig
+++ b/core/java/android/content/pm/xr.aconfig
@@ -6,4 +6,5 @@ flag {
name: "xr_manifest_entries"
description: "Adds manifest entries used by Android XR"
bug: "364416355"
+ is_exported: true
} \ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 7a23033754c5..73b6417a6ba4 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -53,6 +53,7 @@ flag {
namespace: "biometrics_framework"
description: "This flag is for API changes related to Identity Check"
bug: "373424727"
+ is_exported: true
}
flag {
diff --git a/core/java/android/hardware/contexthub/HubEndpointSession.java b/core/java/android/hardware/contexthub/HubEndpointSession.java
index b3d65c1a4cae..cf952cbdbfdc 100644
--- a/core/java/android/hardware/contexthub/HubEndpointSession.java
+++ b/core/java/android/hardware/contexthub/HubEndpointSession.java
@@ -19,7 +19,6 @@ package android.hardware.contexthub;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
import android.hardware.location.ContextHubTransaction;
@@ -71,7 +70,6 @@ public class HubEndpointSession implements AutoCloseable {
* receiving the response for the message.
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public ContextHubTransaction<Void> sendMessage(@NonNull HubMessage message) {
if (mIsClosed.get()) {
throw new IllegalStateException("Session is already closed.");
@@ -122,7 +120,6 @@ public class HubEndpointSession implements AutoCloseable {
* <p>When this function is invoked, the messaging associated with this session is invalidated.
* All futures messages targeted for this client are dropped.
*/
- @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
public void close() {
if (!mIsClosed.getAndSet(true)) {
mCloseGuard.close();
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
index 4a724ceb6665..1c98b4b3f4f5 100644
--- a/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
+++ b/core/java/android/hardware/contexthub/IContextHubEndpoint.aidl
@@ -38,7 +38,6 @@ interface IContextHubEndpoint {
* @throws IllegalArgumentException If the HubEndpointInfo is not valid.
* @throws IllegalStateException If there are too many opened sessions.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
int openSession(in HubEndpointInfo destination, in @nullable HubServiceInfo serviceInfo);
/**
@@ -49,7 +48,6 @@ interface IContextHubEndpoint {
*
* @throws IllegalStateException If the session wasn't opened.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void closeSession(int sessionId, int reason);
/**
@@ -61,13 +59,11 @@ interface IContextHubEndpoint {
*
* @throws IllegalStateException If the session wasn't opened.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void openSessionRequestComplete(int sessionId);
/**
* Unregister this endpoint from the HAL, invalidate the EndpointInfo previously assigned.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void unregister();
/**
@@ -79,7 +75,6 @@ interface IContextHubEndpoint {
* @param transactionCallback Nullable. If the hub message requires a reply, the transactionCallback
* will be set to non-null.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void sendMessage(int sessionId, in HubMessage message,
in @nullable IContextHubTransactionCallback transactionCallback);
@@ -91,6 +86,5 @@ interface IContextHubEndpoint {
* @param messageSeqNumber The message sequence number, this should match a previously received HubMessage.
* @param errorCode The message delivery status detail.
*/
- @EnforcePermission("ACCESS_CONTEXT_HUB")
void sendMessageDeliveryStatus(int sessionId, int messageSeqNumber, byte errorCode);
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index dc4273dd8994..ebb617249993 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -167,6 +167,7 @@ flag {
namespace: "input"
description: "Enables new 25Q2 keycodes"
bug: "365920375"
+ is_exported: true
}
flag {
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 2ba107805569..73c8e3e130a1 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -157,7 +157,7 @@ public class ConversionUtil {
SoundTrigger.RecognitionConfig apiConfig) {
RecognitionConfig aidlConfig = new RecognitionConfig();
aidlConfig.captureRequested = apiConfig.isCaptureRequested();
- // apiConfig.isAllowMultipleTriggers() is ignored by the lower layers.
+ // apiConfig.isMultipleTriggersAllowed() is ignored by the lower layers.
aidlConfig.phraseRecognitionExtras =
new PhraseRecognitionExtra[apiConfig.getKeyphrases().size()];
for (int i = 0; i < apiConfig.getKeyphrases().size(); ++i) {
@@ -178,7 +178,7 @@ public class ConversionUtil {
}
return new SoundTrigger.RecognitionConfig.Builder()
.setCaptureRequested(aidlConfig.captureRequested)
- .setAllowMultipleTriggers(false)
+ .setMultipleTriggersAllowed(false)
.setKeyphrases(keyphrases)
.setData(Arrays.copyOf(aidlConfig.data, aidlConfig.data.length))
.setAudioCapabilities(aidl2apiAudioCapabilities(aidlConfig.audioCapabilities))
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 7745b036bcbe..7c4ddc669968 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1518,7 +1518,7 @@ public class SoundTrigger {
@FlaggedApi(android.media.soundtrigger.Flags.FLAG_MANAGER_API)
public static final class RecognitionConfig implements Parcelable {
private final boolean mCaptureRequested;
- private final boolean mAllowMultipleTriggers;
+ private final boolean mMultipleTriggersAllowed;
private final KeyphraseRecognitionExtra mKeyphrases[];
private final byte[] mData;
private final @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1529,7 +1529,7 @@ public class SoundTrigger {
* {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
*
* @param captureRequested Whether the DSP should capture the trigger sound.
- * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
* triggers.
* @param keyphrases List of keyphrases in the sound model.
* @param data Opaque data for use by system applications who know about voice engine
@@ -1537,11 +1537,11 @@ public class SoundTrigger {
* @param audioCapabilities Bit field encoding of the AudioCapabilities. See
* {@link ModuleProperties.AudioCapabilities} for details.
*/
- private RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ private RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
@Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
@ModuleProperties.AudioCapabilities int audioCapabilities) {
this.mCaptureRequested = captureRequested;
- this.mAllowMultipleTriggers = allowMultipleTriggers;
+ this.mMultipleTriggersAllowed = multipleTriggersAllowed;
this.mKeyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
this.mData = data != null ? data : new byte[0];
this.mAudioCapabilities = audioCapabilities;
@@ -1553,7 +1553,7 @@ public class SoundTrigger {
*
* @deprecated Use {@link Builder} instead.
* @param captureRequested Whether the DSP should capture the trigger sound.
- * @param allowMultipleTriggers Whether the service should restart listening after the DSP
+ * @param multipleTriggersAllowed Whether the service should restart listening after the DSP
* triggers.
* @param keyphrases List of keyphrases in the sound model.
* @param data Opaque data for use by system applications.
@@ -1563,9 +1563,9 @@ public class SoundTrigger {
@UnsupportedAppUsage
@Deprecated
@TestApi
- public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ public RecognitionConfig(boolean captureRequested, boolean multipleTriggersAllowed,
@Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
- this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
+ this(captureRequested, multipleTriggersAllowed, keyphrases, data, 0);
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1593,8 +1593,8 @@ public class SoundTrigger {
* <p><b>Note:</b> This config flag is currently used at the service layer rather than by
* the DSP.
*/
- public boolean isAllowMultipleTriggers() {
- return mAllowMultipleTriggers;
+ public boolean isMultipleTriggersAllowed() {
+ return mMultipleTriggersAllowed;
}
/**
@@ -1627,19 +1627,19 @@ public class SoundTrigger {
private static RecognitionConfig fromParcel(Parcel in) {
boolean captureRequested = in.readBoolean();
- boolean allowMultipleTriggers = in.readBoolean();
+ boolean multipleTriggersAllowed = in.readBoolean();
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
byte[] data = in.createByteArray();
int audioCapabilities = in.readInt();
- return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
- audioCapabilities);
+ return new RecognitionConfig(captureRequested, multipleTriggersAllowed, keyphrases,
+ data, audioCapabilities);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mCaptureRequested);
- dest.writeBoolean(mAllowMultipleTriggers);
+ dest.writeBoolean(mMultipleTriggersAllowed);
dest.writeTypedArray(mKeyphrases, flags);
dest.writeByteArray(mData);
dest.writeInt(mAudioCapabilities);
@@ -1653,7 +1653,7 @@ public class SoundTrigger {
@Override
public String toString() {
return "RecognitionConfig [captureRequested=" + mCaptureRequested
- + ", allowMultipleTriggers=" + mAllowMultipleTriggers + ", keyphrases="
+ + ", multipleTriggersAllowed=" + mMultipleTriggersAllowed + ", keyphrases="
+ Arrays.toString(mKeyphrases) + ", data=" + Arrays.toString(mData)
+ ", audioCapabilities=" + Integer.toHexString(mAudioCapabilities) + "]";
}
@@ -1670,7 +1670,7 @@ public class SoundTrigger {
if (mCaptureRequested != other.mCaptureRequested) {
return false;
}
- if (mAllowMultipleTriggers != other.mAllowMultipleTriggers) {
+ if (mMultipleTriggersAllowed != other.mMultipleTriggersAllowed) {
return false;
}
if (!Arrays.equals(mKeyphrases, other.mKeyphrases)) {
@@ -1690,7 +1690,7 @@ public class SoundTrigger {
final int prime = 31;
int result = 1;
result = prime * result + (mCaptureRequested ? 1 : 0);
- result = prime * result + (mAllowMultipleTriggers ? 1 : 0);
+ result = prime * result + (mMultipleTriggersAllowed ? 1 : 0);
result = prime * result + Arrays.hashCode(mKeyphrases);
result = prime * result + Arrays.hashCode(mData);
result = prime * result + mAudioCapabilities;
@@ -1702,7 +1702,7 @@ public class SoundTrigger {
*/
public static final class Builder {
private boolean mCaptureRequested;
- private boolean mAllowMultipleTriggers;
+ private boolean mMultipleTriggersAllowed;
@Nullable private KeyphraseRecognitionExtra[] mKeyphrases;
@Nullable private byte[] mData;
private @ModuleProperties.AudioCapabilities int mAudioCapabilities;
@@ -1725,12 +1725,12 @@ public class SoundTrigger {
/**
* Sets allow multiple triggers state.
- * @param allowMultipleTriggers Whether the service should restart listening after the
+ * @param multipleTriggersAllowed Whether the service should restart listening after the
* DSP triggers.
* @return the same Builder instance.
*/
- public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
- mAllowMultipleTriggers = allowMultipleTriggers;
+ public @NonNull Builder setMultipleTriggersAllowed(boolean multipleTriggersAllowed) {
+ mMultipleTriggersAllowed = multipleTriggersAllowed;
return this;
}
@@ -1779,7 +1779,7 @@ public class SoundTrigger {
public @NonNull RecognitionConfig build() {
RecognitionConfig config = new RecognitionConfig(
/* captureRequested= */ mCaptureRequested,
- /* allowMultipleTriggers= */ mAllowMultipleTriggers,
+ /* multipleTriggersAllowed= */ mMultipleTriggersAllowed,
/* keyphrases= */ mKeyphrases,
/* data= */ mData,
/* audioCapabilities= */ mAudioCapabilities);
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
index b719a7c6daac..9403f78383dd 100644
--- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -30,6 +30,7 @@ flag {
namespace: "usb"
description: "Feature flag to enable exposing usb speed system api"
bug: "373653182"
+ is_exported: true
}
flag {
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index f7dc7906d50d..6799db3f7471 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -27,4 +27,5 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 3001fbd4fafa..d3de3f5469d2 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -96,6 +96,7 @@ flag {
namespace: "crumpet"
description: "Allow privileged apps to call bugreport generation without enforcing user consent and delegate it to the calling app instead"
bug: "324046728"
+ is_exported: true
}
# This flag guards the private space feature, its APIs, and some of the feature implementations. The flag android.multiuser.Flags.enable_private_space_features exclusively guards all the implementations.
@@ -178,6 +179,7 @@ flag {
namespace: "game"
description: "Feature flag for adding CPU/GPU headroom API"
bug: "346604998"
+ is_exported: true
}
flag {
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d318b6256428..c2b8157de416 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -213,6 +213,7 @@ flag {
namespace: "permissions"
description: "Enable getDeviceId API in OpEventProxyInfo"
bug: "337340961"
+ is_exported: true
}
flag {
@@ -254,6 +255,7 @@ flag {
namespace: "permissions"
description: "New setOnOpNotedCallback API to allow subscribing to only sync ops."
bug: "372910217"
+ is_exported: true
}
flag {
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 09004b3dcf03..34bae46b484c 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -112,6 +112,7 @@ flag {
namespace: "hardware_backed_security"
description: "AFL feature"
bug: "365994454"
+ is_exported: true
}
flag {
@@ -127,6 +128,7 @@ flag {
namespace: "hardware_backed_security"
description: "Feature flag for exposing KeyStore grant APIs"
bug: "351158708"
+ is_exported: true
}
flag {
@@ -134,4 +136,5 @@ flag {
namespace: "biometrics"
description: "Feature flag for Secure Lockdown feature"
bug: "373422357"
+ is_exported: true
} \ No newline at end of file
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 6228bc98b3c2..42dbd3786001 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -78,6 +78,7 @@ flag {
description: "Prevent intent redirect attacks"
bug: "361143368"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 5d0ec73a024b..72569075c2ed 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -2363,7 +2363,6 @@ public abstract class NotificationListenerService extends Service {
// -- parcelable interface --
private RankingMap(Parcel in) {
- final ClassLoader cl = getClass().getClassLoader();
final int count = in.readInt();
mOrderedKeys.ensureCapacity(count);
mRankings.ensureCapacity(count);
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 7225f27c4555..9736345ae3a9 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "wallet_integration"
description: "Option to launch the Wallet app on double-tap of the power button"
bug: "378469025"
+ is_exported: true
}
flag {
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 7d79fd3d44ea..68fd11592fee 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1541,7 +1541,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
mInternalCallback,
new RecognitionConfig.Builder()
.setCaptureRequested(captureTriggerAudio)
- .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setMultipleTriggersAllowed(allowMultipleTriggers)
.setKeyphrases(recognitionExtra)
.setData(data)
.setAudioCapabilities(audioCapabilities)
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f43f172d7d5b..c2e542c57171 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -91,6 +91,7 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
flag {
@@ -196,6 +197,7 @@ flag {
namespace: "text"
description: "Feature flag for adding a TYPE_DURATION to TtsSpan"
bug: "337103893"
+ is_exported: true
}
flag {
@@ -203,6 +205,7 @@ flag {
namespace: "text"
description: "Deprecate the Paint#elegantTextHeight API and stick it to true"
bug: "349519475"
+ is_exported: true
}
flag {
@@ -210,4 +213,5 @@ flag {
namespace: "text"
description: "Make Paint class work for vertical layout text."
bug: "355296926"
+ is_exported: true
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cb6db6cc024a..31330204e8c6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.adpf.Flags.adpfViewrootimplActionDownBoost;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
@@ -1209,6 +1210,8 @@ public final class ViewRootImpl implements ViewParent,
private long mRenderThreadDrawStartTimeNs = -1;
private long mFirstFramePresentedTimeNs = -1;
+ private final boolean mSendPerfHintOnTouch;
+
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1338,6 +1341,8 @@ public final class ViewRootImpl implements ViewParent,
CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock();
mIsSubscribeGranularDisplayEventsEnabled =
com.android.server.display.feature.flags.Flags.subscribeGranularDisplayEvents();
+
+ mSendPerfHintOnTouch = adpfViewrootimplActionDownBoost();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -7112,6 +7117,10 @@ public final class ViewRootImpl implements ViewParent,
+ "touch mode is " + mAttachInfo.mInTouchMode);
if (mAttachInfo.mInTouchMode == inTouchMode) return false;
+ if (inTouchMode && mAttachInfo.mThreadedRenderer != null && mSendPerfHintOnTouch) {
+ mAttachInfo.mThreadedRenderer.notifyExpensiveFrame();
+ }
+
// tell the window manager
try {
IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 330e46af6381..97cf8fc748e8 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -85,7 +85,7 @@ import java.util.function.IntConsumer;
* @see WindowManagerGlobal
* @hide
*/
-public final class WindowManagerImpl implements WindowManager {
+public class WindowManagerImpl implements WindowManager {
private static final String TAG = "WindowManager";
@UnsupportedAppUsage
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e60fc3ae6b47..049ad20fd992 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -8,6 +8,7 @@ flag {
namespace: "accessibility"
description: "Enables new APIs for an app to convey if a node is expanded or collapsed."
bug: "362782536"
+ is_exported: true
}
flag {
@@ -15,6 +16,7 @@ flag {
namespace: "accessibility"
description: "Adds an API to indicate whether a form field (or similar element) is required."
bug: "362784403"
+ is_exported: true
}
flag {
@@ -44,6 +46,7 @@ flag {
namespace: "accessibility"
description: "Enables new extra data key for an AccessibilityService to request character bounds in unmagnified window coordinates."
bug: "375429616"
+ is_exported: true
}
flag {
@@ -88,6 +91,7 @@ flag {
name: "deprecate_accessibility_announcement_apis"
description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
bug: "376727542"
+ is_exported: true
}
flag {
@@ -95,6 +99,7 @@ flag {
name: "deprecate_ani_label_for_apis"
description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
bug: "333783827"
+ is_exported: true
}
flag {
@@ -145,6 +150,7 @@ flag {
name: "global_action_menu"
description: "Allow AccessibilityService to perform GLOBAL_ACTION_MENU"
bug: "334954140"
+ is_exported: true
}
flag {
@@ -152,6 +158,7 @@ flag {
name: "global_action_media_play_pause"
description: "Allow AccessibilityService to perform GLOBAL_ACTION_MEDIA_PLAY_PAUSE"
bug: "334954140"
+ is_exported: true
}
flag {
@@ -230,6 +237,7 @@ flag {
namespace: "accessibility"
description: "Feature flag for supplemental description api"
bug: "375266174"
+ is_exported: true
}
flag {
@@ -237,6 +245,7 @@ flag {
namespace: "accessibility"
description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
bug: "333780959"
+ is_exported: true
}
flag {
@@ -251,6 +260,7 @@ flag {
namespace: "accessibility"
description: "Feature flag for adding tri-state checked api"
bug: "333784774"
+ is_exported: true
}
flag {
@@ -261,11 +271,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
- }
+}
- flag {
+flag {
name: "indeterminate_range_info"
namespace: "accessibility"
description: "Creates a way to create an INDETERMINATE RangeInfo"
bug: "376108874"
- }
+ is_exported: true
+}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 905f350ca6c5..d527007e586e 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -603,7 +603,7 @@ public class AutofillFeatureFlags {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_ENABLE_RELAYOUT,
- false);
+ true);
}
/** @hide */
@@ -611,7 +611,7 @@ public class AutofillFeatureFlags {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
DEVICE_CONFIG_ENABLE_RELATIVE_LOCATION_FOR_RELAYOUT,
- false);
+ true);
}
/** @hide **/
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 52c5af8889ec..1de0474182dd 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,6 +26,7 @@ import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE;
import static android.service.autofill.Flags.FLAG_FILL_DIALOG_IMPROVEMENTS;
+import static android.service.autofill.Flags.relayoutFix;
import static android.view.ContentInfo.SOURCE_AUTOFILL;
import static android.view.autofill.Helper.sDebug;
import static android.view.autofill.Helper.sVerbose;
@@ -1013,7 +1014,7 @@ public final class AutofillManager {
AutofillFeatureFlags.shouldIncludeInvisibleViewInAssistStructure();
mRelayoutFixDeprecated = AutofillFeatureFlags.shouldIgnoreRelayoutWhenAuthPending();
- mRelayoutFix = AutofillFeatureFlags.enableRelayoutFixes();
+ mRelayoutFix = relayoutFix() && AutofillFeatureFlags.enableRelayoutFixes();
mRelativePositionForRelayout = AutofillFeatureFlags.enableRelativeLocationForRelayout();
mIsCredmanIntegrationEnabled = Flags.autofillCredmanIntegration();
}
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index 1c7570efc26b..675e5a1b4804 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -127,6 +127,7 @@ flag {
description: "Feature flag to introduce new frame rate setting APIs on ViewGroup"
bug: "335874198"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 3b6343e7c4ae..641b01054acb 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -116,6 +116,7 @@ flag {
description: "Add a SurfaceView composition order control API."
bug: "341021569"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -124,6 +125,7 @@ flag {
description: "Add APIs to manage SurfacePackage of the parent SurfaceView."
bug: "341021569"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 73abc472be6d..41567fbf8b51 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -165,6 +165,7 @@ flag {
description: "Writing tools API"
bug: "373788889"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -191,4 +192,5 @@ flag {
description: "Verify KeyEvents in IME"
bug: "331730488"
is_fixed_read_only: true
+ is_exported: true
}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 7a01ad340c56..289c5cf4bf85 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -68,7 +68,8 @@ public enum DesktopModeFlags {
Flags::enableDesktopWindowingTaskbarRunningApps, true),
// TODO: b/369763947 - remove this once ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS is ramped up
ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
- ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false),
+ ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(
+ Flags::enableDesktopWindowingEnterTransitions, false),
ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false),
ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
Flags::enableWindowingTransitionHandlersObservers, false),
@@ -77,7 +78,15 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS(
Flags::enableDesktopAppLaunchTransitions, false),
ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
- ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true);
+ ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
+ ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopWindowingEnterTransitionBugfix, false),
+ ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopWindowingExitTransitionsBugfix, false),
+ ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, false),
+ ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchTransitionsBugfix, false);
private static final String TAG = "DesktopModeFlagsUtil";
// Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 65e5679034c8..4fb5fa70c083 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -297,6 +297,7 @@ flag {
namespace: "lse_desktop_experience"
description: "Enables desktop windowing app-to-web education"
bug: "348205896"
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index b2f125dd2821..d5ba32cafebd 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -48,6 +48,7 @@ flag {
namespace: "responsible_apis"
description: "Introduce additional start modes."
bug: "352182359"
+ is_exported: true
}
flag {
@@ -55,6 +56,7 @@ flag {
namespace: "responsible_apis"
description: "Add options parameter to IntentSender.sendIntent."
bug: "339720406"
+ is_exported: true
}
flag {
@@ -63,6 +65,7 @@ flag {
description: "Strict mode flag"
bug: "324089586"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index efacc346ac0a..1ddfe878dfd8 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -14,6 +14,7 @@ flag {
namespace: "systemui"
description: "Support storing different wallpaper crops for different display dimensions. Only effective after rebooting."
bug: "281648899"
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ebbe4830009c..0b034b61c72e 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -368,6 +368,7 @@ flag {
description: "PRIORITY_SYSTEM_NAVIGATION_OBSERVER predictive back API extension"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -376,6 +377,7 @@ flag {
description: "expose timestamp in BackEvent (API extension)"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -384,6 +386,7 @@ flag {
description: "EDGE_NONE swipeEdge option in BackEvent"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
@@ -422,6 +425,7 @@ flag {
description: "Provide pre-make predictive back API extension"
is_fixed_read_only: true
bug: "362938401"
+ is_exported: true
}
flag {
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b7012b68d459..81734a3e2115 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -100,6 +100,7 @@ flag {
name: "touch_pass_through_opt_in"
description: "Requires apps to opt-in to overlay pass through touches and provide APIs to opt-in"
bug: "358129114"
+ is_exported: true
}
flag {
diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
index 324e84cfd62c..c953d88c9482 100644
--- a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
+++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java
@@ -25,6 +25,7 @@ import android.aconfig.nano.Aconfig.parsed_flags;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Flags;
+import android.content.res.XmlResourceParser;
import android.os.Environment;
import android.os.Process;
import android.util.ArrayMap;
@@ -246,12 +247,13 @@ public class AconfigFlags {
negated = true;
featureFlag = featureFlag.substring(1).strip();
}
- Boolean flagValue = getFlagValue(featureFlag);
- if (flagValue == null) {
- flagValue = false;
- }
+ final Boolean flagValue = getFlagValue(featureFlag);
boolean shouldSkip = false;
- if (flagValue == negated) {
+ if (flagValue == null) {
+ Slog.w(LOG_TAG, "Skipping element " + parser.getName()
+ + " due to unknown feature flag " + featureFlag);
+ shouldSkip = true;
+ } else if (flagValue == negated) {
// Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated)
Slog.i(LOG_TAG, "Skipping element " + parser.getName()
+ " behind feature flag " + featureFlag + " = " + flagValue);
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 01c2e9c7cb41..f199159039d6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2174,6 +2174,16 @@
<item>com.android.location.fused</item>
</string-array>
+ <!-- Package name providing population density location support. -->
+ <string name="config_populationDensityProviderPackageName" translatable="false">com.android.location.populationdensity</string>
+
+ <!-- Whether to enable population density provider overlay, which allows the population density provider to
+ be replaced by an app at run-time. When disabled, only the
+ config_populationDensityProviderPackageName package will be searched for a population density
+ provider, otherwise any system package is eligible. Anyone who wants to disable the overlay
+ mechanism can set it to false. -->
+ <bool name="config_enablePopulationDensityProviderOverlay" translatable="false">true</bool>
+
<!-- Package name of the extension software fallback. -->
<string name="config_extensionFallbackPackageName" translatable="false"></string>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index e8063a27d77b..bb76b9fae8d7 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -488,8 +488,12 @@
<java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
<!-- Whether to show the system notification to users whenever there is a change
- in the satellite availability state at the current location. -->
+ in the satellite availability state at the current location. -->
<bool name="config_satellite_should_notify_availability">true</bool>
<java-symbol type="bool" name="config_satellite_should_notify_availability" />
+ <!-- Whether to allow check message datagrams to be sent even when the satellite modem is in
+ not connected state. -->
+ <bool name="config_satellite_allow_check_message_in_not_connected">false</bool>
+ <java-symbol type="bool" name="config_satellite_allow_check_message_in_not_connected" />
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 380b2971b783..a9ade634a248 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2012,6 +2012,8 @@
<java-symbol type="array" name="config_locationProviderPackageNames" />
<java-symbol type="array" name="config_locationDriverAssistancePackageNames" />
<java-symbol type="array" name="config_locationExtraPackageNames" />
+ <java-symbol type="string" name="config_populationDensityProviderPackageName" />
+ <java-symbol type="bool" name="config_enablePopulationDensityProviderOverlay" />
<java-symbol type="array" name="config_testLocationProviders" />
<java-symbol type="array" name="config_defaultNotificationVibePattern" />
<java-symbol type="array" name="config_defaultNotificationVibeWaveform" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2c468605be86..df2b849019a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.dagger;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT;
import static android.window.DesktopModeFlags.ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS;
@@ -854,7 +855,8 @@ public abstract class WMShellModule {
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor) {
return (Flags.enableDesktopWindowingTransitions()
- || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue())
+ || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue()
+ || ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX.isTrue())
? new SpringDragToDesktopTransitionHandler(
context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor)
: new DefaultDragToDesktopTransitionHandler(
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 33d94d5bb474..36904fb5a05f 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
@@ -78,7 +78,10 @@ class DesktopMixedTransitionHandler(
/** Starts close transition and handles or delegates desktop task close animation. */
override fun startRemoveTransition(wct: WindowContainerTransaction?): IBinder {
- if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue) {
+ if (
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue
+ ) {
return freeformTaskTransitionHandler.startRemoveTransition(wct)
}
requireNotNull(wct)
@@ -102,7 +105,8 @@ class DesktopMixedTransitionHandler(
): IBinder {
if (
!Flags.enableFullyImmersiveInDesktop() &&
- !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
) {
return transitions.startTransition(transitionType, wct, /* handler= */ null)
}
@@ -250,7 +254,10 @@ class DesktopMixedTransitionHandler(
minimizeChange?.taskInfo?.taskId,
immersiveExitChange?.taskInfo?.taskId,
)
- if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ if (
+ DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue ||
+ DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+ ) {
// Only apply minimize change reparenting here if we implement the new app launch
// transitions, otherwise this reparenting is handled in the default handler.
minimizeChange?.let {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index a94a40bc39f8..45425e27a078 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -136,6 +136,7 @@ import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel
import java.io.PrintWriter
import java.util.Optional
import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
import java.util.function.Consumer
/** Handles moving tasks in and out of desktop */
@@ -465,12 +466,15 @@ class DesktopTasksController(
taskSurface: SurfaceControl,
) {
logV("startDragToDesktop taskId=%d", taskInfo.taskId)
- interactionJankMonitor.begin(
- taskSurface,
- context,
- handler,
- CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
- )
+ val jankConfigBuilder =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD,
+ context,
+ taskSurface,
+ handler,
+ )
+ .setTimeout(APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS)
+ interactionJankMonitor.begin(jankConfigBuilder)
dragToDesktopTransitionHandler.startDragToDesktopTransition(
taskInfo.taskId,
dragToDesktopValueAnimator,
@@ -1918,7 +1922,10 @@ class DesktopTasksController(
launchTaskId: Int,
minimizeTaskId: Int?,
) {
- if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+ if (
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue &&
+ !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
+ ) {
return
}
// TODO b/359523924: pass immersive task here?
@@ -2635,6 +2642,10 @@ class DesktopTasksController(
val DESKTOP_MODE_INITIAL_BOUNDS_SCALE =
SystemProperties.getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f
+ // Timeout used for CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD, this is longer than the
+ // default timeout to avoid timing out in the middle of a drag action.
+ private val APP_HANDLE_DRAG_HOLD_CUJ_TIMEOUT_MS: Long = TimeUnit.SECONDS.toMillis(10L)
+
private const val TAG = "DesktopTasksController"
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index a1e329af1543..1f03d7568130 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -37,6 +37,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.Cuj;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -44,6 +45,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
@@ -53,6 +55,9 @@ import java.util.function.Supplier;
* If the drag is repositioning, we update in the typical manner.
*/
public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
+ // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+ // timing out in the middle of a resize or drag action.
+ private static final long LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10L);
private DesktopModeWindowDecoration mDesktopWindowDecoration;
private ShellTaskOrganizer mTaskOrganizer;
@@ -106,8 +111,8 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
mRepositionStartPoint.set(x, y);
if (isResizing()) {
// Capture CUJ for re-sizing window in DW mode.
- mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW);
+ mInteractionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_RESIZE_WINDOW));
if (!mDesktopWindowDecoration.mHasGlobalFocus) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true /* onTop */,
@@ -153,8 +158,8 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
}
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
// Begin window drag CUJ instrumentation only when drag position moves.
- mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface,
- mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW);
+ mInteractionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(CUJ_DESKTOP_MODE_DRAG_WINDOW));
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y);
@@ -207,6 +212,14 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T
}
}
+ private InteractionJankMonitor.Configuration.Builder createLongTimeoutJankConfigBuilder(
+ @Cuj.CujType int cujType) {
+ return InteractionJankMonitor.Configuration.Builder
+ .withSurface(cujType, mDesktopWindowDecoration.mContext,
+ mDesktopWindowDecoration.mTaskSurface, mHandler)
+ .setTimeout(LONG_CUJ_TIMEOUT_MS);
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index ddbc681f7cac..f40edaebec5e 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -266,5 +266,26 @@ test_module_config {
test_suites: ["device-tests"],
}
+test_module_config {
+ name: "WMShellFlickerTestsPip-nonMatchParent",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.*"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaExpandButtonTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaExpandButtonTest"],
+ test_suites: ["device-tests"],
+}
+
+test_module_config {
+ name: "WMShellFlickerTestsPip-BottomHalfExitPipToAppViaIntentTest",
+ base: "WMShellFlickerTestsPip",
+ include_filters: ["com.android.wm.shell.flicker.pip.nonmatchparent.BottomHalfExitPipToAppViaIntentTest"],
+ test_suites: ["device-tests"],
+}
+
// End breakdowns for WMShellFlickerTestsPip module
////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
new file mode 100644
index 000000000000..c405b664e431
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppTransition.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.Presubmit
+import android.tools.flicker.legacy.LegacyFlickerTest
+import android.tools.traces.component.ComponentNameMatcher
+import com.android.server.wm.flicker.helpers.BottomHalfPipAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
+import org.junit.Test
+
+/**
+ * Base test class to verify PIP exit animation with an activity layout to the bottom half of
+ * the container.
+ */
+abstract class BottomHalfExitPipToAppTransition(flicker: LegacyFlickerTest) :
+ ExitPipToAppTransition(flicker) {
+
+ override val pipApp: PipAppHelper = BottomHalfPipAppHelper(instrumentation)
+
+ @Presubmit
+ @Test
+ override fun showBothAppLayersThenHidePip() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ @Presubmit
+ @Test
+ override fun showBothAppWindowsThenHidePip() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ @Presubmit
+ @Test
+ override fun pipAppCoversFullScreenAtEnd() {
+ // Disabled since the BottomHalfPipActivity just covers half of the simple activity.
+ }
+
+ /**
+ * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+ * half of screen.
+ */
+ @Presubmit
+ @Test
+ fun showBothAppLayersDuringPipTransition() {
+ flicker.assertLayers {
+ isVisible(testApp)
+ .isVisible(pipApp.or(ComponentNameMatcher.TRANSITION_SNAPSHOT))
+ }
+ }
+
+ /**
+ * Checks that the [testApp] and [pipApp] are always visible since the [pipApp] only covers
+ * half of screen.
+ */
+ @Presubmit
+ @Test
+ fun showBothAppWindowsDuringPipTransition() {
+ flicker.assertWm {
+ isAppWindowVisible(testApp)
+ .isAppWindowOnTop(pipApp)
+ .isAppWindowVisible(pipApp)
+ }
+ }
+
+ /**
+ * Verify that the [testApp] and [pipApp] covers the entire screen at the end of PIP exit
+ * animation since the [pipApp] will use a bottom half layout.
+ */
+ @Presubmit
+ @Test
+ fun testPlusPipAppCoversWindowFrameAtEnd() {
+ flicker.assertLayersEnd {
+ val pipRegion = visibleRegion(pipApp).region
+ visibleRegion(testApp).plus(pipRegion).coversExactly(displayBounds)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
new file mode 100644
index 000000000000..2a3dc07037df
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaExpandButtonTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via the expand button
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaExpandButtonTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in pip mode [bottomHalfPipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [bottomHalfPipApp] app to bottom half layout by clicking on the pip window and
+ * then on the expand button
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaExpandButtonTest(flicker: LegacyFlickerTest) :
+ BottomHalfExitPipToAppTransition(flicker)
+{
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.expandPipWindowToApp(wmHelper)
+ // Wait until the transition idle and test and pip app still shows.
+ wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+ .withAppTransitionIdle().waitForAndVerify()
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
new file mode 100644
index 000000000000..8ed9cd23005b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/nonmatchparent/BottomHalfExitPipToAppViaIntentTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip.nonmatchparent
+
+import android.platform.test.annotations.RequiresDevice
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.tools.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.flicker.legacy.FlickerBuilder
+import android.tools.flicker.legacy.LegacyFlickerTest
+import com.android.window.flags.Flags
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test expanding a pip window back to bottom half layout via an intent
+ *
+ * To run this test: `atest WMShellFlickerTestsPip:BottomHalfExitPipToAppViaIntentTest`
+ *
+ * Actions:
+ * ```
+ * Launch an app in pip mode [bottomHalfPipApp],
+ * Launch another full screen mode [testApp]
+ * Expand [bottomHalfPipApp] app to bottom half layout via an intent
+ * ```
+ *
+ * Notes:
+ * ```
+ * 1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ * are inherited from [PipTransition]
+ * 2. Part of the test setup occurs automatically via
+ * [android.tools.flicker.legacy.runner.TransitionRunner],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ * ```
+ */
+// TODO(b/380796448): re-enable tests after the support of non-match parent PIP animation for PIP2.
+@RequiresFlagsDisabled(com.android.wm.shell.Flags.FLAG_ENABLE_PIP2)
+@RequiresFlagsEnabled(Flags.FLAG_BETTER_SUPPORT_NON_MATCH_PARENT_ACTIVITY)
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class BottomHalfExitPipToAppViaIntentTest(flicker: LegacyFlickerTest) :
+ BottomHalfExitPipToAppTransition(flicker)
+{
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.exitPipToFullScreenViaIntent(wmHelper)
+ // Wait until the transition idle and test and pip app still shows.
+ wmHelper.StateSyncBuilder().withLayerVisible(testApp).withLayerVisible(pipApp)
+ .withAppTransitionIdle().waitForAndVerify()
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 2d5544527436..267bbb6c51e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -152,7 +152,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @DisableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startRemoveTransition_callsFreeformTaskTransitionHandler() {
val wct = WindowContainerTransaction()
whenever(freeformTaskTransitionHandler.startRemoveTransition(wct))
@@ -164,7 +166,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startRemoveTransition_startsCloseTransition() {
val wct = WindowContainerTransaction()
whenever(transitions.startTransition(WindowManager.TRANSIT_CLOSE, wct, mixedHandler))
@@ -203,7 +207,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startAnimation_withClosingDesktopTask_callsCloseTaskHandler() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
@@ -231,7 +237,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX)
fun startAnimation_withClosingLastDesktopTask_dispatchesTransition() {
val wct = WindowContainerTransaction()
val transition = mock<IBinder>()
@@ -272,7 +280,8 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@DisableFlags(
Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
- Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -308,7 +317,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
val wct = WindowContainerTransaction()
val task = createTask(WINDOWING_MODE_FREEFORM)
@@ -407,7 +418,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -437,7 +450,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -469,7 +484,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -566,7 +583,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
@@ -598,7 +617,9 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
+ Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index fa27af671be6..e497ea1f3cb4 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "core_graphics"
description: "API for AGSL authored runtime color filters and blenders"
bug: "358126864"
+ is_exported: true
}
flag {
@@ -44,6 +45,7 @@ flag {
namespace: "accessibility"
description: "Draw a solid rectangle background behind text instead of a stroke outline"
bug: "186567103"
+ is_exported: true
}
flag {
@@ -96,6 +98,7 @@ flag {
namespace: "core_graphics"
description: "Add canvas#drawRegion API"
bug: "318612129"
+ is_exported: true
}
flag {
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index cf3f74085d66..8cd08d3aad6c 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -642,6 +642,14 @@ package android.location.provider {
method public void onFlushComplete();
}
+ @FlaggedApi("android.location.flags.population_density_provider") public abstract class PopulationDensityProviderBase {
+ ctor public PopulationDensityProviderBase(@NonNull android.content.Context, @NonNull String);
+ method @Nullable public final android.os.IBinder getBinder();
+ method public abstract void onGetCoarsenedS2Cell(double, double, @NonNull android.os.OutcomeReceiver<long[],java.lang.Throwable>);
+ method public abstract void onGetDefaultCoarseningLevel(@NonNull android.os.OutcomeReceiver<java.lang.Integer,java.lang.Throwable>);
+ field public static final String ACTION_POPULATION_DENSITY_PROVIDER = "com.android.location.service.PopulationDensityProvider";
+ }
+
public final class ProviderRequest implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=0) public long getIntervalMillis();
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 24e1d32164d0..5395206155b3 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "location"
description: "Deprecates LocationManager ProviderChanged APIs"
bug: "361811782"
+ is_exported: true
}
flag {
@@ -27,6 +28,7 @@ flag {
namespace: "location"
description: "Flag for new Geocoder APIs"
bug: "229872126"
+ is_exported: true
}
flag {
@@ -56,6 +58,7 @@ flag {
namespace: "location"
description: "Flag for making geoid heights available via the Altitude HAL"
bug: "304375846"
+ is_exported: true
}
flag {
@@ -63,6 +66,7 @@ flag {
namespace: "location"
description: "Flag for GNSS API for NavIC L1"
bug: "302199306"
+ is_exported: true
}
flag {
@@ -70,6 +74,7 @@ flag {
namespace: "location"
description: "Flag for GnssMeasurementRequest WorkSource API"
bug: "295235160"
+ is_exported: true
}
flag {
@@ -129,6 +134,7 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+ is_exported: true
}
flag {
diff --git a/location/java/android/location/provider/IPopulationDensityProvider.aidl b/location/java/android/location/provider/IPopulationDensityProvider.aidl
new file mode 100644
index 000000000000..9b5cb5ae8c7a
--- /dev/null
+++ b/location/java/android/location/provider/IPopulationDensityProvider.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.os.Bundle;
+
+import android.location.Location;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+
+/**
+ * Binder interface for services that implement a population density provider. Do not implement this
+ * directly, extend {@link PopulationDensityProviderBase} instead.
+ * @hide
+ */
+oneway interface IPopulationDensityProvider {
+ /**
+ * Gets the default S2 level to be used to coarsen any location, in case a more precise answer
+ * from the method below can't be obtained.
+ */
+ void getDefaultCoarseningLevel(in IS2LevelCallback callback);
+
+ /**
+ * Returns a list of IDs of the S2 cells to be used to coarsen a location. The answer should
+ * contain at least one S2 cell, which should contain the requested location. Its level
+ * represents the population density. Optionally, additional nearby cells can be also returned,
+ * to assist in coarsening nearby locations.
+ */
+ void getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees, in IS2CellIdsCallback
+ callback);
+}
diff --git a/location/java/android/location/provider/IS2CellIdsCallback.aidl b/location/java/android/location/provider/IS2CellIdsCallback.aidl
new file mode 100644
index 000000000000..f583045ebb26
--- /dev/null
+++ b/location/java/android/location/provider/IS2CellIdsCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 cell IDs callbacks.
+ * @hide
+ */
+oneway interface IS2CellIdsCallback {
+
+ /**
+ * Called with the resulting list of S2 cell IDs. The first cell is expected to contain
+ * the requested latitude/longitude. Its level represent the population density. Optionally,
+ * the list can also contain additional nearby cells.
+ */
+ void onResult(in long[] s2CellIds);
+
+ /** Called if any error occurs while processing the query. */
+ void onError();
+}
diff --git a/location/java/android/location/provider/IS2LevelCallback.aidl b/location/java/android/location/provider/IS2LevelCallback.aidl
new file mode 100644
index 000000000000..49f96ef7e3e2
--- /dev/null
+++ b/location/java/android/location/provider/IS2LevelCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.location.Location;
+
+/**
+ * Binder interface for S2 level callback.
+ * @hide
+ */
+oneway interface IS2LevelCallback {
+ /**
+ * Called with the resulting default S2 level for coarsening a location, in case a better
+ * answer cannot be obtained for a latitude/longitude.
+ */
+ void onResult(int s2Level);
+
+ /** Called if any error occurs while processing the query. */
+ void onError();
+}
diff --git a/location/java/android/location/provider/PopulationDensityProviderBase.java b/location/java/android/location/provider/PopulationDensityProviderBase.java
new file mode 100644
index 000000000000..3907516f6aaa
--- /dev/null
+++ b/location/java/android/location/provider/PopulationDensityProviderBase.java
@@ -0,0 +1,192 @@
+/*
+ * 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.location.provider;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.location.flags.Flags;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A provider for population density.
+ * The population density is defined as the S2 level at which the S2 cell around the latitude /
+ * longitude contains at least a thousand people.
+ * It exposes two methods: one about providing population density around a latitude / longitude,
+ * and one about providing a "default" population density to fall back to in case the first API
+ * can't be used or returns an error.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public abstract class PopulationDensityProviderBase {
+
+ final String mTag;
+ final @Nullable String mAttributionTag;
+ final IBinder mBinder;
+
+ /**
+ * The action the wrapping service should have in its intent filter to implement the
+ * PopulationDensity provider.
+ */
+ @SuppressLint("ActionValue")
+ public static final String ACTION_POPULATION_DENSITY_PROVIDER =
+ "com.android.location.service.PopulationDensityProvider";
+
+ public PopulationDensityProviderBase(@NonNull Context context, @NonNull String tag) {
+ mTag = tag;
+ mAttributionTag = context.getAttributionTag();
+ mBinder = new Service();
+ }
+
+ /**
+ * Returns the IBinder instance that should be returned from the
+ * {@link android.app.Service#onBind(Intent)} method of the wrapping service.
+ */
+ public final @Nullable IBinder getBinder() {
+ return mBinder;
+ }
+
+ /**
+ * Called upon receiving a new request for the default coarsening level.
+ * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case
+ * an error occurs, {@link OutcomeReceiver#onError} should be called.
+ * The callback is single-use, calling more than any one of these two methods throws an
+ * AssertionException.
+ *
+ * @param callback A single-use callback that either returns the coarsening level, or an error.
+ */
+ public abstract void onGetDefaultCoarseningLevel(@NonNull OutcomeReceiver<Integer, Throwable>
+ callback);
+
+ /**
+ * Called upon receiving a new request for population density at a specific latitude/longitude,
+ * expressed in degrees.
+ * The answer is at least one S2CellId corresponding to the coarsening level at the specified
+ * location. This must be the first element of the result array. Optionally, additional nearby
+ * S2CellIds can be returned. One use for the optional nearby cells is when the client has a
+ * local cache that needs to be filled with the local area around a certain latitude/longitude.
+ * The callback {@link OutcomeReceiver#onResult} should be called with the result; or, in case
+ * an error occurs, {@link OutcomeReceiver#onError} should be called.
+ * The callback is single-use, calling more than any one of these two methods throws an
+ * AssertionException.
+ *
+ * @param callback A single-use callback that either returns S2CellIds, or an error.
+ */
+ public abstract void onGetCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees,
+ @NonNull OutcomeReceiver<long[], Throwable> callback);
+
+ private final class Service extends IPopulationDensityProvider.Stub {
+ @Override
+ public void getDefaultCoarseningLevel(@NonNull IS2LevelCallback callback) {
+ try {
+ onGetDefaultCoarseningLevel(new SingleUseS2LevelCallback(callback));
+ } catch (RuntimeException e) {
+ // exceptions on one-way binder threads are dropped - move to a different thread
+ Log.w(mTag, e);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ throw new AssertionError(e);
+ });
+ }
+ }
+
+ @Override
+ public void getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees,
+ @NonNull IS2CellIdsCallback callback) {
+ try {
+ onGetCoarsenedS2Cell(latitudeDegrees, longitudeDegrees,
+ new SingleUseS2CellIdsCallback(callback));
+ } catch (RuntimeException e) {
+ // exceptions on one-way binder threads are dropped - move to a different thread
+ Log.w(mTag, e);
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ throw new AssertionError(e);
+ });
+ }
+ }
+ }
+
+ private static class SingleUseS2LevelCallback implements OutcomeReceiver<Integer, Throwable> {
+
+ private final AtomicReference<IS2LevelCallback> mCallback;
+
+ SingleUseS2LevelCallback(IS2LevelCallback callback) {
+ mCallback = new AtomicReference<>(callback);
+ }
+
+ @Override
+ public void onResult(Integer level) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onResult(level);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+ } catch (RemoteException r) {
+ throw r.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ private static class SingleUseS2CellIdsCallback implements OutcomeReceiver<long[], Throwable> {
+
+ private final AtomicReference<IS2CellIdsCallback> mCallback;
+
+ SingleUseS2CellIdsCallback(IS2CellIdsCallback callback) {
+ mCallback = new AtomicReference<>(callback);
+ }
+
+ @Override
+ public void onResult(long[] s2CellIds) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onResult(s2CellIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ try {
+ Objects.requireNonNull(mCallback.getAndSet(null)).onError();
+ } catch (RemoteException r) {
+ throw r.rethrowFromSystemServer();
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index a14f1fd15f27..547099f044b2 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -19,6 +19,7 @@ package android.media;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -37,6 +38,7 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -81,6 +83,22 @@ public abstract class MediaRoute2ProviderService extends Service {
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
/**
+ * {@link Intent} action that indicates that the declaring service supports routing of the
+ * system media.
+ *
+ * <p>Providers must include this action if they intend to publish routes that support the
+ * system media, as described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+ *
+ * @see #onCreateSystemRoutingSession
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE_SYSTEM_MEDIA =
+ "android.media.MediaRoute2ProviderService.SYSTEM_MEDIA";
+
+ /**
* A category indicating that the associated provider is only intended for use within the app
* that hosts the provider.
*
@@ -138,12 +156,26 @@ public abstract class MediaRoute2ProviderService extends Service {
public static final int REASON_INVALID_COMMAND = 4;
/**
+ * The request has failed because the requested operation is not implemented by the provider.
+ *
+ * @see #notifyRequestFailed
* @hide
*/
- @IntDef(prefix = "REASON_", value = {
- REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE,
- REASON_INVALID_COMMAND
- })
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final int REASON_UNIMPLEMENTED = 5;
+
+ /** @hide */
+ @IntDef(
+ prefix = "REASON_",
+ value = {
+ REASON_UNKNOWN_ERROR,
+ REASON_REJECTED,
+ REASON_NETWORK_ERROR,
+ REASON_ROUTE_NOT_AVAILABLE,
+ REASON_INVALID_COMMAND,
+ REASON_UNIMPLEMENTED
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface Reason {}
@@ -282,6 +314,32 @@ public abstract class MediaRoute2ProviderService extends Service {
}
/**
+ * Notifies the system of the successful creation of a system media routing session.
+ *
+ * <p>This method can only be called as the result of a prior call to {@link
+ * #onCreateSystemRoutingSession}.
+ *
+ * @param requestId the ID of the {@link #onCreateSystemRoutingSession} request which this call
+ * is in response to.
+ * @param sessionInfo a {@link RoutingSessionInfo} that describes the newly created routing
+ * session.
+ * @param formats the {@link MediaStreamsFormats} that describes the format for the {@link
+ * MediaStreams} to return.
+ * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
+ * newly created routing session.
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ @NonNull
+ public final MediaStreams notifySystemMediaSessionCreated(
+ long requestId,
+ @NonNull RoutingSessionInfo sessionInfo,
+ @NonNull MediaStreamsFormats formats) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* Notifies the existing session is updated. For example, when
* {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
*/
@@ -399,6 +457,43 @@ public abstract class MediaRoute2ProviderService extends Service {
@NonNull String routeId, @Nullable Bundle sessionHints);
/**
+ * Called when the service receives a request to create a system routing session.
+ *
+ * <p>This method will only be called for routes that support routing of the system media, as
+ * described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
+ *
+ * <p>Implementors of this method must call {@link #notifySystemMediaSessionCreated} with the
+ * given {@code requestId} to indicate a successful session creation. If the session creation
+ * fails (for example, if the connection to the receiver device fails), the implementor must
+ * call {@link #notifyRequestFailed}, passing the {@code requestId}.
+ *
+ * <p>Unlike {@link #onCreateSession}, system sessions route the system media (for example,
+ * audio and/or video) which is to be retrieved by calling {@link
+ * #notifySystemMediaSessionCreated}.
+ *
+ * <p>Changes to the session can be notified by calling {@link #notifySessionUpdated}.
+ *
+ * @param requestId the ID of this request
+ * @param packageName the package name of the application whose media to route.
+ * @param routeId the ID of the route initially being {@link
+ * RoutingSessionInfo#getSelectedRoutes() selected}.
+ * @param sessionHints an optional bundle of arguments sent by {@link MediaRouter2}, or null if
+ * none.
+ * @see RoutingSessionInfo.Builder
+ * @see #notifySystemMediaSessionCreated
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public void onCreateSystemRoutingSession(
+ long requestId,
+ @NonNull String packageName,
+ @NonNull String routeId,
+ @Nullable Bundle sessionHints) {
+ mHandler.post(() -> notifyRequestFailed(requestId, REASON_UNIMPLEMENTED));
+ }
+
+ /**
* Called when the session should be released. A client of the session or system can request
* a session to be released.
* <p>
@@ -735,4 +830,100 @@ public abstract class MediaRoute2ProviderService extends Service {
MediaRoute2ProviderService.this, requestId, sessionId));
}
}
+
+ /**
+ * Holds the streams to be routed as part of a system media routing session.
+ *
+ * <p>The encoded data format matches the {@link MediaStreamsFormats} passed to {@link
+ * #notifySystemMediaSessionCreated}.
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class MediaStreams {
+
+ private final AudioRecord mAudioRecord;
+
+ // TODO: b/380431086: Add the video equivalent.
+
+ private MediaStreams(AudioRecord mAudioRecord) {
+ this.mAudioRecord = mAudioRecord;
+ }
+
+ /**
+ * Returns the {@link AudioRecord} from which to read the audio data to route, or null if
+ * the routing session doesn't include audio.
+ */
+ @Nullable
+ public AudioRecord getAudioRecord() {
+ return mAudioRecord;
+ }
+ }
+
+ /**
+ * Holds the formats to encode media data to be read from {@link MediaStreams}.
+ *
+ * @see MediaStreams
+ * @see #notifySystemMediaSessionCreated
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class MediaStreamsFormats {
+
+ private final AudioFormat mAudioFormat;
+
+ // TODO: b/380431086: Add the video equivalent.
+
+ private MediaStreamsFormats(Builder builder) {
+ this.mAudioFormat = builder.mAudioFormat;
+ }
+
+ /**
+ * Returns the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+ * return from {@link #notifySystemMediaSessionCreated}.
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public AudioFormat getAudioFormat() {
+ return mAudioFormat;
+ }
+
+ /**
+ * Builder for {@link MediaStreamsFormats}
+ *
+ * @hide
+ */
+ // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+ @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+ public static final class Builder {
+ private AudioFormat mAudioFormat;
+
+ /**
+ * Sets the audio format to use for creating the {@link MediaStreams#getAudioRecord} to
+ * return from {@link #notifySystemMediaSessionCreated}.
+ *
+ * @param audioFormat the audio format
+ * @return this builder
+ */
+ @NonNull
+ public Builder setAudioFormat(@NonNull AudioFormat audioFormat) {
+ this.mAudioFormat = Objects.requireNonNull(audioFormat);
+ return this;
+ }
+
+ /**
+ * Builds the {@link MediaStreamsFormats} instance.
+ *
+ * @return the built {@link MediaStreamsFormats} instance
+ */
+ @NonNull
+ public MediaStreamsFormats build() {
+ return new MediaStreamsFormats(this);
+ }
+ }
+ }
}
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
index b4dee0cab24e..fa1349c61c4c 100644
--- a/media/java/android/media/flags/projection.aconfig
+++ b/media/java/android/media/flags/projection.aconfig
@@ -16,4 +16,16 @@ flag {
name: "stop_media_projection_on_call_end"
description: "Stops MediaProjection sessions when a call ends"
bug: "368336349"
-} \ No newline at end of file
+}
+
+flag {
+ name: "media_projection_connected_display_no_virtual_device"
+ namespace: "media_projection"
+ description: "Filter out display associated with a virtual device for media projection use case"
+ bug: "362720120"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_exported: true
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 65e83b9bf204..8fe543656ed9 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -328,7 +328,7 @@ public final class SoundTriggerDetector {
mRecognitionCallback,
new RecognitionConfig.Builder()
.setCaptureRequested(captureTriggerAudio)
- .setAllowMultipleTriggers(allowMultipleTriggers)
+ .setMultipleTriggersAllowed(allowMultipleTriggers)
.setAudioCapabilities(audioCapabilities)
.build(),
runInBatterySaver);
diff --git a/native/android/OWNERS b/native/android/OWNERS
index f0db2ea236ea..1fde7d268517 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -2,7 +2,7 @@ jreck@google.com #{LAST_RESORT_SUGGESTION}
# General NDK API reviewers
per-file libandroid.map.txt = danalbert@google.com, etalvala@google.com, michaelwr@google.com
-per-file libandroid.map.txt = jreck@google.com, zyy@google.com
+per-file libandroid.map.txt = jreck@google.com, zyy@google.com, mattbuckley@google.com
# Networking
per-file libandroid_net.map.txt, net.c = set noparent
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 077d7d350064..e8644ee1a73c 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -301,7 +301,6 @@ LIBANDROID {
ASurfaceTransaction_setEnableBackPressure; # introduced=31
ASurfaceTransaction_setFrameRate; # introduced=30
ASurfaceTransaction_setFrameRateWithChangeStrategy; # introduced=31
- ASurfaceTransaction_setFrameRateParams; # introduced=36
ASurfaceTransaction_clearFrameRate; # introduced=34
ASurfaceTransaction_setFrameTimeline; # introduced=Tiramisu
ASurfaceTransaction_setGeometry; # introduced=29
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index fc64e9b48f6d..6bca1456db3a 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -794,28 +794,6 @@ void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* aSu
transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
}
-void ASurfaceTransaction_setFrameRateParams(
- ASurfaceTransaction* aSurfaceTransaction, ASurfaceControl* aSurfaceControl,
- float desiredMinRate, float desiredMaxRate, float fixedSourceRate,
- ANativeWindow_ChangeFrameRateStrategy changeFrameRateStrategy) {
- CHECK_NOT_NULL(aSurfaceTransaction);
- CHECK_NOT_NULL(aSurfaceControl);
- Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
- sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
-
- if (desiredMaxRate < desiredMinRate) {
- ALOGW("desiredMaxRate must be greater than or equal to desiredMinRate");
- return;
- }
- // TODO(b/362798998): Fix plumbing to send modern params
- int compatibility = fixedSourceRate == 0 ? ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT
- : ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
- double frameRate = compatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
- ? fixedSourceRate
- : desiredMinRate;
- transaction->setFrameRate(surfaceControl, frameRate, compatibility, changeFrameRateStrategy);
-}
-
void ASurfaceTransaction_clearFrameRate(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl) {
CHECK_NOT_NULL(aSurfaceTransaction);
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
index 0cd0b3cb14f1..19818e0b06da 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_icon_frame.xml
@@ -22,7 +22,7 @@
android:minWidth="@dimen/settingslib_expressive_space_medium3"
android:minHeight="@dimen/settingslib_expressive_space_medium3"
android:gravity="center"
- android:layout_marginEnd="-4dp"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall6"
android:filterTouchesWhenObscured="false">
<androidx.preference.internal.PreferenceImageView
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index 944bef6c9e09..c837ff43e46b 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,7 +21,8 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingVertical="@dimen/settingslib_expressive_space_small1"
- android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
+ android:paddingStart="@dimen/settingslib_expressive_space_none"
+ android:paddingEnd="@dimen/settingslib_expressive_space_small1"
android:filterTouchesWhenObscured="false">
<TextView
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 9764e64b8509..4428480eaa3e 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -75,6 +75,7 @@ open class TopIntroPreference @JvmOverloads constructor(
(holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
setCollapsable(isCollapsable)
setMinLines(minLines)
+ visibility = if (title.isNullOrEmpty()) View.GONE else View.VISIBLE
setText(title.toString())
if (hyperlinkListener != null) {
setHyperlinkListener(hyperlinkListener)
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 89de995fa5ef..2d13add1e15f 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -98,6 +98,7 @@ flag {
namespace: "android_settings"
description: "Settings catalyst project migration"
bug: "323791114"
+ is_exported: true
}
flag {
@@ -106,6 +107,7 @@ flag {
namespace: "android_settings"
description: "Enable WRITE_SYSTEM_PREFERENCE permission and appop"
bug: "375193223"
+ is_exported: true
}
flag {
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
deleted file mode 100644
index 645b275e2af5..000000000000
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 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.
--->
-
-<!--
- Default system bookmarks for AOSP.
- Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
-
- Typical shortcuts (not necessarily defined here):
- 'b': Browser
- 'c': Contacts
- 'e': Email
- 'g': GMail
- 'k': Calendar
- 'm': Maps
- 'p': Music
- 's': SMS
- 't': Talk
- 'u': Calculator
- 'y': YouTube
--->
-<bookmarks>
- <!-- TODO(b/358569822): Remove this from Settings DB
- This is legacy implementation to store bookmarks in Settings DB, which is deprecated and
- no longer used -->
- <bookmark
- role="android.app.role.BROWSER"
- shortcut="b" />
- <bookmark
- category="android.intent.category.APP_CONTACTS"
- shortcut="c" />
- <bookmark
- category="android.intent.category.APP_EMAIL"
- shortcut="e" />
- <bookmark
- category="android.intent.category.APP_CALENDAR"
- shortcut="k" />
- <bookmark
- category="android.intent.category.APP_MAPS"
- shortcut="m" />
- <bookmark
- category="android.intent.category.APP_MUSIC"
- shortcut="p" />
- <bookmark
- role="android.app.role.SMS"
- shortcut="s" />
- <bookmark
- category="android.intent.category.APP_CALCULATOR"
- shortcut="u" />
-</bookmarks>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index e85ba453945b..e057682d6d95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -16,14 +16,8 @@
package com.android.providers.settings;
-import android.content.ComponentName;
-import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
@@ -46,16 +40,11 @@ import android.util.Log;
import com.android.internal.content.InstallLocationUtils;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.RILConstants;
-import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockscreenCredential;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.File;
-import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -85,7 +74,7 @@ class DatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
private int mUserHandle;
- private static final HashSet<String> mValidTables = new HashSet<String>();
+ private static final HashSet<String> mValidTables = new HashSet<>();
private static final String DATABASE_BACKUP_SUFFIX = "-backup";
@@ -100,7 +89,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
// These are old.
mValidTables.add("bluetooth_devices");
- mValidTables.add("bookmarks");
mValidTables.add("favorites");
mValidTables.add("old_favorites");
mValidTables.add("android_metadata");
@@ -211,21 +199,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
"type INTEGER" +
");");
- db.execSQL("CREATE TABLE bookmarks (" +
- "_id INTEGER PRIMARY KEY," +
- "title TEXT," +
- "folder TEXT," +
- "intent TEXT," +
- "shortcut INTEGER," +
- "ordering INTEGER" +
- ");");
-
- db.execSQL("CREATE INDEX bookmarksIndex1 ON bookmarks (folder);");
- db.execSQL("CREATE INDEX bookmarksIndex2 ON bookmarks (shortcut);");
-
- // Populate bookmarks table with initial bookmarks
- loadBookmarks(db);
-
// Load initial volume levels into DB
loadVolumeLevels(db);
@@ -392,19 +365,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
}
if (upgradeVersion == 30) {
- /*
- * Upgrade 31 clears the title for all quick launch shortcuts so the
- * activities' titles will be resolved at display time. Also, the
- * folder is changed to '@quicklaunch'.
- */
- db.beginTransaction();
- try {
- db.execSQL("UPDATE bookmarks SET folder = '@quicklaunch'");
- db.execSQL("UPDATE bookmarks SET title = ''");
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
upgradeVersion = 31;
}
@@ -1006,8 +966,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
}
if (upgradeVersion == 70) {
- // Update all built-in bookmarks. Some of the package names have changed.
- loadBookmarks(db);
upgradeVersion = 71;
}
@@ -2046,92 +2004,6 @@ class DatabaseHelper extends SQLiteOpenHelper {
}
/**
- * Loads the default set of bookmarked shortcuts from an xml file.
- *
- * @param db The database to write the values into
- */
- private void loadBookmarks(SQLiteDatabase db) {
- ContentValues values = new ContentValues();
-
- PackageManager packageManager = mContext.getPackageManager();
- try {
- XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
- XmlUtils.beginDocument(parser, "bookmarks");
-
- final int depth = parser.getDepth();
- int type;
-
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- String name = parser.getName();
- if (!"bookmark".equals(name)) {
- break;
- }
-
- String pkg = parser.getAttributeValue(null, "package");
- String cls = parser.getAttributeValue(null, "class");
- String shortcutStr = parser.getAttributeValue(null, "shortcut");
- String category = parser.getAttributeValue(null, "category");
-
- int shortcutValue = shortcutStr.charAt(0);
- if (TextUtils.isEmpty(shortcutStr)) {
- Log.w(TAG, "Unable to get shortcut for: " + pkg + "/" + cls);
- continue;
- }
-
- final Intent intent;
- final String title;
- if (pkg != null && cls != null) {
- ActivityInfo info = null;
- ComponentName cn = new ComponentName(pkg, cls);
- try {
- info = packageManager.getActivityInfo(cn, 0);
- } catch (PackageManager.NameNotFoundException e) {
- String[] packages = packageManager.canonicalToCurrentPackageNames(
- new String[] { pkg });
- cn = new ComponentName(packages[0], cls);
- try {
- info = packageManager.getActivityInfo(cn, 0);
- } catch (PackageManager.NameNotFoundException e1) {
- Log.w(TAG, "Unable to add bookmark: " + pkg + "/" + cls, e);
- continue;
- }
- }
-
- intent = new Intent(Intent.ACTION_MAIN, null);
- intent.addCategory(Intent.CATEGORY_LAUNCHER);
- intent.setComponent(cn);
- title = info.loadLabel(packageManager).toString();
- } else if (category != null) {
- intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, category);
- title = "";
- } else {
- Log.w(TAG, "Unable to add bookmark for shortcut " + shortcutStr
- + ": missing package/class or category attributes");
- continue;
- }
-
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- values.put(Settings.Bookmarks.INTENT, intent.toUri(0));
- values.put(Settings.Bookmarks.TITLE, title);
- values.put(Settings.Bookmarks.SHORTCUT, shortcutValue);
- db.delete("bookmarks", "shortcut = ?",
- new String[] { Integer.toString(shortcutValue) });
- db.insert("bookmarks", null, values);
- }
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Got execption parsing bookmarks.", e);
- } catch (IOException e) {
- Log.w(TAG, "Got execption parsing bookmarks.", e);
- }
- }
-
- /**
* Loads the default volume levels. It is actually inserting the index of
* the volume array for each of the volume controls.
*
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 38f09988e7a7..3eeaf41d874a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -424,15 +424,16 @@ constructor(
newKeyguardOccludedState: Boolean?
) {
super.onTransitionAnimationCancelled(newKeyguardOccludedState)
- cleanUp()
+ onDispose()
}
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
super.onTransitionAnimationEnd(isExpandingFullyAbove)
- cleanUp()
+ onDispose()
}
- private fun cleanUp() {
+ override fun onDispose() {
+ super.onDispose()
cleanUpRunnable?.run()
}
}
@@ -560,6 +561,7 @@ constructor(
cookie: TransitionCookie? = null,
component: ComponentName? = null,
returnCujType: Int? = null,
+ isEphemeral: Boolean = true,
): Controller? {
// Make sure the View we launch from implements LaunchableView to avoid visibility
// issues.
@@ -587,6 +589,7 @@ constructor(
cookie,
component,
returnCujType,
+ isEphemeral,
)
}
}
@@ -647,6 +650,9 @@ constructor(
* appropriately.
*/
fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean? = null) {}
+
+ /** The controller will not be used again. Clean up the relevant internal state. */
+ fun onDispose() {}
}
/**
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
index 3ba9a2974846..b56a68cb2dd6 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt
@@ -39,7 +39,8 @@ interface Expandable {
launchCujType: Int? = null,
cookie: ActivityTransitionAnimator.TransitionCookie? = null,
component: ComponentName? = null,
- returnCujType: Int? = null
+ returnCujType: Int? = null,
+ isEphemeral: Boolean = true,
): ActivityTransitionAnimator.Controller?
/**
@@ -55,7 +56,8 @@ interface Expandable {
launchCujType,
cookie = null,
component = null,
- returnCujType = null
+ returnCujType = null,
+ isEphemeral = true,
)
}
@@ -80,14 +82,16 @@ interface Expandable {
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
return ActivityTransitionAnimator.Controller.fromView(
view,
launchCujType,
cookie,
component,
- returnCujType
+ returnCujType,
+ isEphemeral,
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index e626c04675e1..558c1eba2c1c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -67,6 +67,12 @@ constructor(
/** The [CujType] associated to this return animation. */
private val returnCujType: Int? = null,
+
+ /**
+ * Whether this controller should be invalidated after its first use, and whenever [ghostedView]
+ * is detached.
+ */
+ private val isEphemeral: Boolean = false,
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
) : ActivityTransitionAnimator.Controller {
@@ -119,6 +125,19 @@ constructor(
returnCujType
}
+ /**
+ * Used to automatically clean up the internal state once [ghostedView] is detached from the
+ * hierarchy.
+ */
+ private val detachListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {}
+
+ override fun onViewDetachedFromWindow(v: View) {
+ onDispose()
+ }
+ }
+
init {
// Make sure the View we launch from implements LaunchableView to avoid visibility issues.
if (ghostedView !is LaunchableView) {
@@ -155,6 +174,16 @@ constructor(
}
background = findBackground(ghostedView)
+
+ if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+ ghostedView.addOnAttachStateChangeListener(detachListener)
+ }
+ }
+
+ override fun onDispose() {
+ if (TransitionAnimator.returnAnimationsEnabled()) {
+ ghostedView.removeOnAttachStateChangeListener(detachListener)
+ }
}
/**
@@ -164,7 +193,7 @@ constructor(
protected open fun setBackgroundCornerRadius(
background: Drawable,
topCornerRadius: Float,
- bottomCornerRadius: Float
+ bottomCornerRadius: Float,
) {
// By default, we rely on WrappedDrawable to set/restore the background radii before/after
// each draw.
@@ -195,7 +224,7 @@ constructor(
val state =
TransitionAnimator.State(
topCornerRadius = getCurrentTopCornerRadius(),
- bottomCornerRadius = getCurrentBottomCornerRadius()
+ bottomCornerRadius = getCurrentBottomCornerRadius(),
)
fillGhostedViewState(state)
return state
@@ -269,7 +298,7 @@ constructor(
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
val ghostView = this.ghostView ?: return
val backgroundView = this.backgroundView!!
@@ -317,11 +346,11 @@ constructor(
scale,
scale,
ghostedViewState.centerX - transitionContainerLocation[0],
- ghostedViewState.centerY - transitionContainerLocation[1]
+ ghostedViewState.centerY - transitionContainerLocation[1],
)
ghostViewMatrix.postTranslate(
(leftChange + rightChange) / 2f,
- (topChange + bottomChange) / 2f
+ (topChange + bottomChange) / 2f,
)
ghostView.animationMatrix = ghostViewMatrix
@@ -462,7 +491,7 @@ constructor(
private fun updateRadii(
radii: FloatArray,
topCornerRadius: Float,
- bottomCornerRadius: Float
+ bottomCornerRadius: Float,
) {
radii[0] = topCornerRadius
radii[1] = topCornerRadius
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
index cbe11a3f2f60..8a57e8cbbb20 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS;
+import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -163,7 +164,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner
t.show(wallpapers[i].leash);
t.setAlpha(wallpapers[i].leash, 1.f);
}
- if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()) {
+ if (ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()
+ || ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()) {
resetLauncherAlphaOnDesktopExit(info, launcherTask, leashMap, t);
}
} else {
diff --git a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
index efbdf4d1533d..0abeeb7d62a8 100644
--- a/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
+++ b/packages/SystemUI/common/src/com/android/systemui/coroutines/Tracing.kt
@@ -20,7 +20,7 @@ import com.android.app.tracing.coroutines.createCoroutineTracingContext
import kotlin.coroutines.CoroutineContext
fun newTracingContext(name: String): CoroutineContext {
- return createCoroutineTracingContext(name, walkStackForDefaultNames = true) { className ->
+ return createCoroutineTracingContext(name, walkStackForDefaultNames = false) { className ->
className.startsWith("com.android.systemui.util.kotlin.JavaAdapter") ||
className.startsWith("com.android.systemui.lifecycle.RepeatWhenAttached")
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index a55df2b36a80..103a9b5cf5f4 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -52,6 +52,9 @@ import kotlin.math.roundToInt
interface ExpandableController {
/** The [Expandable] controlled by this controller. */
val expandable: Expandable
+
+ /** Called when the [Expandable] stop being included in the composition. */
+ fun onDispose()
}
/**
@@ -88,33 +91,44 @@ fun rememberExpandableController(
// Whether this composable is still composed. We only do the dialog exit animation if this is
// true.
val isComposed = remember { mutableStateOf(true) }
- DisposableEffect(Unit) { onDispose { isComposed.value = false } }
-
- return remember(
- color,
- contentColor,
- shape,
- borderStroke,
- composeViewRoot,
- density,
- layoutDirection,
- ) {
- ExpandableControllerImpl(
+
+ val controller =
+ remember(
color,
contentColor,
shape,
borderStroke,
composeViewRoot,
density,
- animatorState,
- isDialogShowing,
- overlay,
- currentComposeViewInOverlay,
- boundsInComposeViewRoot,
layoutDirection,
- isComposed,
- )
+ ) {
+ ExpandableControllerImpl(
+ color,
+ contentColor,
+ shape,
+ borderStroke,
+ composeViewRoot,
+ density,
+ animatorState,
+ isDialogShowing,
+ overlay,
+ currentComposeViewInOverlay,
+ boundsInComposeViewRoot,
+ layoutDirection,
+ isComposed,
+ )
+ }
+
+ DisposableEffect(Unit) {
+ onDispose {
+ isComposed.value = false
+ if (TransitionAnimator.returnAnimationsEnabled()) {
+ controller.onDispose()
+ }
+ }
}
+
+ return controller
}
internal class ExpandableControllerImpl(
@@ -132,19 +146,29 @@ internal class ExpandableControllerImpl(
private val layoutDirection: LayoutDirection,
private val isComposed: State<Boolean>,
) : ExpandableController {
+ /** The [ActivityTransitionAnimator.Controller] to be cleaned up [onDispose]. */
+ private var activityControllerForDisposal: ActivityTransitionAnimator.Controller? = null
+
override val expandable: Expandable =
object : Expandable {
override fun activityTransitionController(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
if (!isComposed.value) {
return null
}
- return activityController(launchCujType, cookie, component, returnCujType)
+ val controller = activityController(launchCujType, cookie, component, returnCujType)
+ if (TransitionAnimator.returnAnimationsEnabled() && isEphemeral) {
+ activityControllerForDisposal?.onDispose()
+ activityControllerForDisposal = controller
+ }
+
+ return controller
}
override fun dialogTransitionController(
@@ -158,6 +182,11 @@ internal class ExpandableControllerImpl(
}
}
+ override fun onDispose() {
+ activityControllerForDisposal?.onDispose()
+ activityControllerForDisposal = null
+ }
+
/**
* Create a [TransitionAnimator.Controller] that is going to be used to drive an activity or
* dialog animation. This controller will:
@@ -181,7 +210,7 @@ internal class ExpandableControllerImpl(
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
// We copy state given that it's always the same object that is mutated by
// ActivityTransitionAnimator.
@@ -269,7 +298,7 @@ internal class ExpandableControllerImpl(
launchCujType: Int?,
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
- returnCujType: Int?
+ returnCujType: Int?,
): ActivityTransitionAnimator.Controller {
val delegate = transitionController()
return object :
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 4bccac1e3ba0..86c5fd824d8f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -28,7 +28,6 @@ import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastForEach
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.SharedElementTransformation
-import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
@@ -528,39 +527,6 @@ internal class MutableSceneTransitionLayoutStateImpl(
transitionStates = listOf(TransitionState.Idle(scene, currentOverlays))
}
- /**
- * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
- * to the closest scene.
- *
- * Important: Snapping to the closest scene will instantly finish *all* ongoing transitions,
- * only the progress of the last transition will be checked.
- *
- * @return true if snapped to the closest scene.
- */
- internal fun snapToIdleIfClose(threshold: Float): Boolean {
- val transition = currentTransition ?: return false
- val progress = transition.progress
-
- fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
-
- fun finishAllTransitions() {
- // Force finish all transitions.
- while (currentTransitions.isNotEmpty()) {
- finishTransition(transitionStates[0] as TransitionState.Transition)
- }
- }
-
- val shouldSnap =
- (isProgressCloseTo(0f) && transition.isFromCurrentContent()) ||
- (isProgressCloseTo(1f) && transition.isToCurrentContent())
- return if (shouldSnap) {
- finishAllTransitions()
- true
- } else {
- false
- }
- }
-
override fun showOverlay(
overlay: OverlayKey,
animationScope: CoroutineScope,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 79ca891babd1..3b7d661ba91a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -18,12 +18,9 @@ package com.android.compose.animation.scene
import android.util.Log
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.compose.animation.scene.TestOverlays.OverlayA
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
@@ -169,130 +166,6 @@ class SceneTransitionLayoutStateTest {
assertThat(state.currentTransition?.transformationSpec?.transformationMatchers).hasSize(2)
}
- @Test
- fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(from = SceneA, to = SceneB, current = { SceneA }, progress = { 0.2f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the initial scene if it is close to 0.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- }
-
- @Test
- fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the initial scene if it is close to 0.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
- }
-
- @Test
- fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(from = SceneA, to = SceneB, progress = { 0.8f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
- }
-
- @Test
- fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.isTransitioning()).isFalse()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA)))
- }
-
- @Test
- fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
-
- val aToB = transition(from = SceneA, to = SceneB, progress = { 0.5f })
- state.startTransitionImmediately(animationScope = backgroundScope, aToB)
- assertThat(state.currentTransitions).containsExactly(aToB).inOrder()
-
- val bToC = transition(from = SceneB, to = SceneC, progress = { 0.8f })
- state.startTransitionImmediately(animationScope = backgroundScope, bToC)
- assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
- // Ignore the request if the progress is not close to 0 or 1, using the threshold.
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.currentTransitions).containsExactly(aToB, bToC).inOrder()
-
- // Go to the final scene if it is close to 1.
- assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
- assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneC))
- assertThat(state.currentTransitions).isEmpty()
- }
-
- @Test
- fun snapToIdleIfClose_closeButNotCurrentScene() = runMonotonicClockTest {
- val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
- var progress by mutableStateOf(0f)
- var currentScene by mutableStateOf(SceneB)
- state.startTransitionImmediately(
- animationScope = backgroundScope,
- transition(
- from = SceneA,
- to = SceneB,
- current = { currentScene },
- progress = { progress },
- ),
- )
- assertThat(state.isTransitioning()).isTrue()
-
- // Ignore the request if we are close to a scene that is not the current scene
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
-
- progress = 1f
- currentScene = SceneA
- assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
- assertThat(state.isTransitioning()).isTrue()
- }
-
private fun MonotonicClockTestScope.startOverscrollableTransistionFromAtoB(
progress: () -> Float,
sceneTransitions: SceneTransitions,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index 72916a35814f..d12c04586ac2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -36,14 +36,14 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.ALL_SUPPORTED_MODIFIERS
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutCategory
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
@@ -88,7 +88,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
- fun categories_emitsCorrectlyConvertedShortcutCategories() {
+ fun categories_correctlyConvertsAPIModelsToShortcutHelperModels() {
testScope.runTest {
whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
.thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
@@ -323,4 +323,33 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
}
}
}
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun categories_isUpdatedAfterCustomShortcutsAreReset() {
+ testScope.runTest {
+ // TODO(b/380445594) refactor tests and move these stubbings to ShortcutHelperTestHelper
+ var customInputGestures = listOf(allAppsInputGestureData)
+ whenever(inputManager.getCustomInputGestures(anyOrNull())).then {
+ return@then customInputGestures
+ }
+ whenever(
+ inputManager.removeAllCustomInputGestures(
+ /* filter = */ InputGestureData.Filter.KEY
+ )
+ )
+ .then {
+ customInputGestures = emptyList()
+ return@then CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
+ }
+
+ val categories by collectLastValue(repo.categories)
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+
+ assertThat(categories).containsExactly(allAppsShortcutCategory)
+ repo.resetAllCustomShortcuts()
+ assertThat(categories).isEmpty()
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt
new file mode 100644
index 000000000000..60d70897a6fa
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSourceTest.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.shortcut.data.source
+
+import android.content.res.mainResources
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MultitaskingShortcutsSourceTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val source = MultitaskingShortcutsSource(kosmos.mainResources, context)
+
+ @Test
+ fun shortcutGroups_doesNotContainCycleThroughRecentAppsShortcuts() {
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts =
+ groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+ val cycleThroughRecentAppsShortcuts =
+ listOf(
+ Triple(
+ context.getString(R.string.group_system_cycle_forward),
+ META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ Triple(
+ context.getString(R.string.group_system_cycle_back),
+ META_SHIFT_ON or META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ )
+
+ assertThat(shortcuts).containsNoneIn(cycleThroughRecentAppsShortcuts)
+ }
+ }
+
+ private companion object {
+ private const val TEST_DEVICE_ID = 1234
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
index 495e98d0edfb..b9fb3e64777d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSourceTest.kt
@@ -26,6 +26,9 @@ import android.view.KeyEvent
import android.view.KeyEvent.KEYCODE_BACK
import android.view.KeyEvent.KEYCODE_HOME
import android.view.KeyEvent.KEYCODE_RECENT_APPS
+import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_SHIFT_ON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
@@ -132,7 +135,33 @@ class SystemShortcutsSourceTest : SysuiTestCase() {
assertThat(shortcuts).doesNotContain(hardwareShortcut)
}
- companion object {
+ @Test
+ fun shortcutGroups_containsCycleThroughRecentAppsShortcuts() {
+ testScope.runTest {
+ val groups = source.shortcutGroups(TEST_DEVICE_ID)
+
+ val shortcuts =
+ groups.flatMap { it.items }.map { c -> Triple(c.label, c.modifiers, c.keycode) }
+
+ val cycleThroughRecentAppsShortcuts =
+ listOf(
+ Triple(
+ context.getString(R.string.group_system_cycle_forward),
+ META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ Triple(
+ context.getString(R.string.group_system_cycle_back),
+ META_SHIFT_ON or META_ALT_ON,
+ KEYCODE_TAB,
+ ),
+ )
+
+ assertThat(shortcuts).containsAtLeastElementsIn(cycleThroughRecentAppsShortcuts)
+ }
+ }
+
+ private companion object {
private const val TEST_DEVICE_ID = 1234
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 7855d4219788..c287da8df135 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -49,7 +49,6 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shared.model.shortcut
import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
-import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.res.R
object TestShortcuts {
@@ -492,17 +491,15 @@ object TestShortcuts {
simpleShortcutCategory(AppCategories, "Applications", "Email"),
simpleShortcutCategory(AppCategories, "Applications", "Maps"),
simpleShortcutCategory(AppCategories, "Applications", "SMS"),
- simpleShortcutCategory(MultiTasking, "Recent apps", "Cycle forward through recent apps"),
)
- val customInputGestureTypeHome =
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ val customInputGestureTypeHome = simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME)
val allCustomizableInputGesturesWithSimpleShortcutCombinations =
listOf(
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
),
- simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ simpleInputGestureData(keyGestureType = KEY_GESTURE_TYPE_HOME),
simpleInputGestureData(
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
),
@@ -626,9 +623,6 @@ object TestShortcuts {
}
}
- val expectedStandardDeleteShortcutUiState =
- ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false)
-
val keyDownEventWithoutActionKeyPressed =
androidx.compose.ui.input.key.KeyEvent(
android.view.KeyEvent(
@@ -671,12 +665,4 @@ object TestShortcuts {
categoryType = ShortcutCategoryType.System,
subCategoryLabel = "Standard subcategory",
)
-
- val expectedStandardAddShortcutUiState =
- ShortcutCustomizationUiState.AddShortcutDialog(
- shortcutLabel = "Standard shortcut",
- defaultCustomShortcutModifierKey =
- ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
- isDialogShowing = false,
- )
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index d3d1a3506725..2d05ee0fe234 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -28,25 +28,26 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardAddShortcutUiState
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardDeleteShortcutUiState
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutAddRequest
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddShortcutRequest
-import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsShortcutDeleteRequest
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.userTracker
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -62,8 +63,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
private val mockUserContext: Context = mock()
private val kosmos =
- Kosmos().also {
- it.testCase = this
+ testKosmos().also {
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
private val testScope = kosmos.testScope
@@ -92,7 +92,23 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- assertThat(uiState).isEqualTo(expectedStandardAddShortcutUiState)
+ assertThat(uiState).isEqualTo(
+ AddShortcutDialog(
+ shortcutLabel = "Standard shortcut",
+ defaultCustomShortcutModifierKey =
+ ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ )
+ )
+ }
+ }
+
+ @Test
+ fun uiState_correctlyUpdatedWhenResetShortcutCustomizationIsRequested() {
+ testScope.runTest {
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+
+ assertThat(uiState).isEqualTo(ResetShortcutDialog())
}
}
@@ -102,7 +118,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onShortcutCustomizationRequested(allAppsShortcutDeleteRequest)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- assertThat(uiState).isEqualTo(expectedStandardDeleteShortcutUiState)
+ assertThat(uiState).isEqualTo(DeleteShortcutDialog())
}
}
@@ -113,7 +129,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
viewModel.onDialogShown()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).isDialogShowing)
+ assertThat((uiState as AddShortcutDialog).isDialogShowing)
.isTrue()
}
}
@@ -126,13 +142,25 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onDialogShown()
assertThat(
- (uiState as ShortcutCustomizationUiState.DeleteShortcutDialog).isDialogShowing
+ (uiState as DeleteShortcutDialog).isDialogShowing
)
.isTrue()
}
}
@Test
+ fun uiState_consumedOnResetDialogShown() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ viewModel.onDialogShown()
+
+ assertThat((uiState as ResetShortcutDialog).isDialogShowing)
+ .isTrue()
+ }
+ }
+
+ @Test
fun uiState_inactiveAfterDialogIsDismissed() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
@@ -148,7 +176,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.isEmpty()
}
}
@@ -173,7 +201,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onShortcutCustomizationRequested(allAppsShortcutAddRequest)
viewModel.onDialogShown()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEmpty()
}
}
@@ -187,7 +215,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(
context.getString(
R.string.shortcut_customizer_key_combination_in_use_error_message
@@ -205,7 +233,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(
context.getString(
R.string.shortcut_customizer_key_combination_in_use_error_message
@@ -223,7 +251,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
openAddShortcutDialogAndSetShortcut()
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
+ assertThat((uiState as AddShortcutDialog).errorMessage)
.isEqualTo(context.getString(R.string.shortcut_customizer_generic_error_message))
}
}
@@ -244,6 +272,18 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
+ fun uiState_becomesInactiveAfterSuccessfullyResettingShortcuts() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.getCustomInputGestures(any())).thenReturn(emptyList())
+
+ openResetShortcutDialogAndResetAllCustomShortcuts()
+
+ assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+ }
+ }
+
+ @Test
fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
testScope.runTest {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
@@ -272,7 +312,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
}
}
@@ -286,13 +326,13 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onKeyPressed(keyUpEventWithActionKeyPressed)
// Note that Action Key is excluded as it's already displayed on the UI
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.containsExactly(ShortcutKey.Text("Ctrl"), ShortcutKey.Text("A"))
// Close the dialog and show it again
viewModel.onDialogDismissed()
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
- assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).pressedKeys)
+ assertThat((uiState as AddShortcutDialog).pressedKeys)
.isEmpty()
}
}
@@ -313,4 +353,11 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.deleteShortcutCurrentlyBeingCustomized()
}
+
+ private suspend fun openResetShortcutDialogAndResetAllCustomShortcuts() {
+ viewModel.onShortcutCustomizationRequested(ShortcutCustomizationRequestInfo.Reset)
+ viewModel.onDialogShown()
+
+ viewModel.resetAllCustomShortcuts()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 534c12cc0407..3a4c993fd8b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -18,7 +18,10 @@ package com.android.systemui.screenrecord
import android.content.Intent
import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.hardware.display.VirtualDisplayConfig
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
import android.testing.TestableLooper
import android.view.View
import android.widget.Spinner
@@ -42,6 +45,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
import junit.framework.Assert.assertEquals
import org.junit.After
import org.junit.Before
@@ -224,6 +228,34 @@ class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
.notifyProjectionRequestCancelled(TEST_HOST_UID)
}
+ @Test
+ @RequiresFlagsEnabled(
+ com.android.media.projection.flags.Flags
+ .FLAG_MEDIA_PROJECTION_CONNECTED_DISPLAY_NO_VIRTUAL_DEVICE
+ )
+ fun doNotShowVirtualDisplayInDialog() {
+ val displayManager = context.getSystemService(DisplayManager::class.java)!!
+ var virtualDisplay: VirtualDisplay? = null
+ try {
+ virtualDisplay =
+ displayManager.createVirtualDisplay(
+ VirtualDisplayConfig.Builder("virtual display", 1, 1, 160).build()
+ )
+ showDialog()
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ val adapter = spinner.adapter
+ val virtualDisplayAvailable =
+ (0 until adapter.count)
+ .mapNotNull { adapter.getItem(it) as? String }
+ .any { it.contains("virtual display", ignoreCase = true) }
+ assertWithMessage("A Virtual Display was shown in the list of display to record")
+ .that(virtualDisplayAvailable)
+ .isFalse()
+ } finally {
+ virtualDisplay?.release()
+ }
+ }
+
private fun showDialog() {
dialog.show()
}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4bf67a12963a..a0a61c74369f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3736,10 +3736,6 @@
that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are
for example "Enter split screen". [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_category_multitasking">Multitasking</string>
- <!-- Title of the keyboard shortcut helper category "Recent apps". The helper is a component
- that shows the user which keyboard shortcuts they can use. The "Recent apps" shortcuts are
- for example "Cycle through recent apps". [CHAR LIMIT=NONE] -->
- <string name="shortcutHelper_category_recent_apps">Recent apps</string>
<!-- Title of the keyboard shortcut helper category "Split screen". The helper is a component
that shows the user which keyboard shortcuts they can use. The "Split screen" shortcuts are
for example "Move current app to left split". [CHAR LIMIT=NONE] -->
@@ -3772,6 +3768,11 @@
The helper is a component that shows the user which keyboard shortcuts they can use. Also
allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
<string name="shortcut_customize_mode_remove_shortcut_dialog_title">Remove shortcut?</string>
+ <!-- Title at the top of the keyboard shortcut helper reset shortcut dialog. This dialog allows
+ the user to remove all custom shortcuts the user has set, resetting to default shortcuts only.
+ Shortcut helper is a component that shows the user which keyboard shortcuts they can use. Also
+ allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
+ <string name="shortcut_customize_mode_reset_shortcut_dialog_title">Reset back to default?</string>
<!-- Sub title at the top of the keyboard shortcut helper customization dialog. Explains to the
user what action they need to take in the customization dialog to assign a new custom shortcut.
The shortcut customize dialog allows users to add/remove custom shortcuts
@@ -3782,6 +3783,10 @@
users to add/remove custom shortcuts
[CHAR LIMIT=NONE] -->
<string name="shortcut_customize_mode_remove_shortcut_description">This will delete your custom shortcut permanently.</string>
+ <!-- Sub title at the top of the reset custom shortcut dialog. Explains to the user that the action
+ they're about to take will remove all custom shortcuts they have set, resetting to default shortcuts only.
+ The shortcut customize dialog allows users to add/remove custom shortcuts [CHAR LIMIT=NONE] -->
+ <string name="shortcut_customize_mode_reset_shortcut_description">This will delete all your custom shortcuts permanently.</string>
<!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
hasn't typed in anything in the search box yet. The helper is a component that shows the
user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3849,6 +3854,10 @@
confirm and remove previously added custom shortcut. The helper is a component that
shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
<string name="shortcut_helper_customize_dialog_remove_button_label">Remove</string>
+ <!-- Label on the reset shortcut button in keyboard shortcut helper customize dialog, that allows user to
+ confirm and reset all added custom shortcut. The helper is a component that
+ shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_customize_dialog_reset_button_label">Yes, reset</string>
<!-- Label on the cancel button in keyboard shortcut helper customize dialog, that allows user to
cancel and exit shortcut customization dialog, returning to the main shortcut helper page.
The helper is a component that shows the user which keyboard shortcuts they can use.
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
index 215ceacaef14..0ed4007d68ef 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
@@ -78,9 +78,16 @@ fun Expandable.withStateAwareness(
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? =
delegate
- .activityTransitionController(launchCujType, cookie, component, returnCujType)
+ .activityTransitionController(
+ launchCujType,
+ cookie,
+ component,
+ returnCujType,
+ isEphemeral,
+ )
?.withStateAwareness(onActivityLaunchTransitionStart, onActivityLaunchTransitionEnd)
override fun dialogTransitionController(
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index b82aa817afd8..1504402279b4 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -284,6 +284,7 @@ constructor(
cookie: ActivityTransitionAnimator.TransitionCookie?,
component: ComponentName?,
returnCujType: Int?,
+ isEphemeral: Boolean,
): ActivityTransitionAnimator.Controller? {
val delegatedController =
ActivityTransitionAnimator.Controller.fromView(
@@ -292,6 +293,7 @@ constructor(
cookie,
component,
returnCujType,
+ isEphemeral,
)
return delegatedController?.let { createTransitionControllerDelegate(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
index 9ffdafc549c7..36cd40052041 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
@@ -57,7 +57,11 @@ constructor(private val userTracker: UserTracker,
_customInputGesture.onStart { refreshCustomInputGestures() }
private fun refreshCustomInputGestures() {
- _customInputGesture.value = retrieveCustomInputGestures()
+ setCustomInputGestures(inputGestures = retrieveCustomInputGestures())
+ }
+
+ private fun setCustomInputGestures(inputGestures: List<InputGestureData>) {
+ _customInputGesture.value = inputGestures
}
fun retrieveCustomInputGestures(): List<InputGestureData> {
@@ -112,6 +116,22 @@ constructor(private val userTracker: UserTracker,
}
}
+ suspend fun resetAllCustomInputGestures(): ShortcutCustomizationRequestResult {
+ return withContext(bgCoroutineContext) {
+ try {
+ inputManager.removeAllCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ setCustomInputGestures(emptyList())
+ SUCCESS
+ } catch (e: Exception) {
+ Log.w(
+ TAG,
+ "Attempted to remove all custom shortcut but ran into a remote error: $e",
+ )
+ ERROR_OTHER
+ }
+ }
+ }
+
private companion object {
private const val TAG = "CustomInputGesturesRepository"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index d1bd51c23d45..4af378646db3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -169,7 +169,8 @@ constructor(
.firstOrNull { it.action.keyGestureType() == keyGestureType }
}
- suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+ suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
+ ShortcutCustomizationRequestResult {
val inputGestureData =
buildInputGestureDataForShortcutBeingCustomized()
?: return ShortcutCustomizationRequestResult.ERROR_OTHER
@@ -184,6 +185,10 @@ constructor(
return customInputGesturesRepository.deleteCustomInputGesture(inputGestureData)
}
+ suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+ return customInputGesturesRepository.resetAllCustomInputGestures()
+ }
+
private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel()
@@ -297,17 +302,13 @@ constructor(
return null
}
- private fun fetchGroupLabelByGestureType(
- @KeyGestureType keyGestureType: Int
- ): String? {
+ private fun fetchGroupLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
inputGestureMaps.gestureToInternalKeyboardShortcutGroupLabelResIdMap[keyGestureType]?.let {
return context.getString(it)
} ?: return null
}
- private fun fetchShortcutInfoLabelByGestureType(
- @KeyGestureType keyGestureType: Int
- ): String? {
+ private fun fetchShortcutInfoLabelByGestureType(@KeyGestureType keyGestureType: Int): String? {
inputGestureMaps.gestureToInternalKeyboardShortcutInfoLabelResIdMap[keyGestureType]?.let {
return context.getString(it)
} ?: return null
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index ecc076178d2d..1c380c26c6c3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -104,7 +104,6 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.shortcut_helper_category_system_apps,
// Multitasking Category
- KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER to R.string.shortcutHelper_category_recent_apps,
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to
R.string.shortcutHelper_category_split_screen,
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index 5ef869e6d848..df6b04e2afd3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -26,11 +26,9 @@ import android.view.KeyEvent.KEYCODE_EQUALS
import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
import android.view.KeyEvent.KEYCODE_MINUS
import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
-import android.view.KeyEvent.KEYCODE_TAB
import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
-import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -49,13 +47,9 @@ constructor(@Main private val resources: Resources, @Application private val con
override suspend fun shortcutGroups(deviceId: Int) =
listOf(
KeyboardShortcutGroup(
- resources.getString(R.string.shortcutHelper_category_recent_apps),
- recentsShortcuts(),
- ),
- KeyboardShortcutGroup(
resources.getString(R.string.shortcutHelper_category_split_screen),
splitScreenShortcuts(),
- ),
+ )
)
private fun splitScreenShortcuts() = buildList {
@@ -140,18 +134,4 @@ constructor(@Main private val resources: Resources, @Application private val con
)
}
}
-
- private fun recentsShortcuts() =
- listOf(
- // Cycle through recent apps (forward):
- // - Alt + Tab
- shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
- command(META_ALT_ON, KEYCODE_TAB)
- },
- // Cycle through recent apps (back):
- // - Shift + Alt + Tab
- shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
- command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
- },
- )
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
index a650cd889381..687ad9550b16 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/SystemShortcutsSource.kt
@@ -21,9 +21,7 @@ import android.hardware.input.InputManager
import android.hardware.input.KeyGlyphMap
import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_BACK
-import android.view.KeyEvent.KEYCODE_DEL
import android.view.KeyEvent.KEYCODE_DPAD_LEFT
-import android.view.KeyEvent.KEYCODE_ENTER
import android.view.KeyEvent.KEYCODE_ESCAPE
import android.view.KeyEvent.KEYCODE_H
import android.view.KeyEvent.KEYCODE_HOME
@@ -34,8 +32,10 @@ import android.view.KeyEvent.KEYCODE_RECENT_APPS
import android.view.KeyEvent.KEYCODE_S
import android.view.KeyEvent.KEYCODE_SLASH
import android.view.KeyEvent.KEYCODE_TAB
+import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CTRL_ON
import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
import com.android.systemui.Flags.shortcutHelperKeyGlyph
@@ -127,29 +127,31 @@ constructor(@Main private val resources: Resources, private val inputManager: In
},
// Access home screen:
// - Meta + H
- // - Meta + Enter
shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
command(META_META_ON, KEYCODE_H)
},
- shortcutInfo(resources.getString(R.string.group_system_access_home_screen)) {
- command(META_META_ON, KEYCODE_ENTER)
- },
// Overview of open apps:
// - Meta + Tab
shortcutInfo(resources.getString(R.string.group_system_overview_open_apps)) {
command(META_META_ON, KEYCODE_TAB)
},
+ // Cycle through recent apps (forward):
+ // - Alt + Tab
+ shortcutInfo(resources.getString(R.string.group_system_cycle_forward)) {
+ command(META_ALT_ON, KEYCODE_TAB)
+ },
+ // Cycle through recent apps (back):
+ // - Shift + Alt + Tab
+ shortcutInfo(resources.getString(R.string.group_system_cycle_back)) {
+ command(META_SHIFT_ON or META_ALT_ON, KEYCODE_TAB)
+ },
// Back: go back to previous state (back button)
// - Meta + Escape OR
- // - Meta + Backspace OR
// - Meta + Left arrow
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
command(META_META_ON, KEYCODE_ESCAPE)
},
shortcutInfo(resources.getString(R.string.group_system_go_back)) {
- command(META_META_ON, KEYCODE_DEL)
- },
- shortcutInfo(resources.getString(R.string.group_system_go_back)) {
command(META_META_ON, KEYCODE_DPAD_LEFT)
},
// Take a full screenshot:
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index 7743c53c6900..ef242678a8ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -46,8 +46,11 @@ constructor(private val customShortcutRepository: CustomShortcutCategoriesReposi
return customShortcutRepository.confirmAndSetShortcutCurrentlyBeingCustomized()
}
- suspend fun deleteShortcutCurrentlyBeingCustomized():
- ShortcutCustomizationRequestResult {
+ suspend fun deleteShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
return customShortcutRepository.deleteShortcutCurrentlyBeingCustomized()
}
+
+ suspend fun resetAllCustomShortcuts(): ShortcutCustomizationRequestResult {
+ return customShortcutRepository.resetAllCustomShortcuts()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
index 2d3e7f6f6448..095de41237cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCustomizationRequestInfo.kt
@@ -28,4 +28,6 @@ sealed interface ShortcutCustomizationRequestInfo {
val categoryType: ShortcutCategoryType = ShortcutCategoryType.System,
val subCategoryLabel: String = "",
) : ShortcutCustomizationRequestInfo
+
+ data object Reset : ShortcutCustomizationRequestInfo
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index f28618bb8cf4..bd0430bf96c3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -31,6 +31,7 @@ import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutCustomizatio
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.statusbar.phone.SystemUIDialogFactory
@@ -54,7 +55,8 @@ constructor(
viewModel.shortcutCustomizationUiState.collect { uiState ->
val shouldShowAddDialog = uiState is AddShortcutDialog && !uiState.isDialogShowing
val shouldShowDeleteDialog = uiState is DeleteShortcutDialog && !uiState.isDialogShowing
- if (shouldShowDeleteDialog || shouldShowAddDialog) {
+ val shouldShowResetDialog = uiState is ResetShortcutDialog && !uiState.isDialogShowing
+ if (shouldShowDeleteDialog || shouldShowAddDialog || shouldShowResetDialog) {
dialog = createDialog().also { it.show() }
viewModel.onDialogShown()
} else if (uiState is ShortcutCustomizationUiState.Inactive) {
@@ -83,7 +85,12 @@ constructor(
onKeyPress = { viewModel.onKeyPressed(it) },
onCancel = { dialog.dismiss() },
onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } },
- onConfirmDeleteShortcut = { coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() } },
+ onConfirmDeleteShortcut = {
+ coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() }
+ },
+ onConfirmResetShortcut = {
+ coroutineScope.launch { viewModel.resetAllCustomShortcuts() }
+ },
)
dialog.setOnDismissListener { viewModel.onDialogDismissed() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
index 20040c673994..ac6708ae983e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -68,73 +68,133 @@ fun ShortcutCustomizationDialog(
onCancel: () -> Unit,
onConfirmSetShortcut: () -> Unit,
onConfirmDeleteShortcut: () -> Unit,
+ onConfirmResetShortcut: () -> Unit,
) {
when (uiState) {
is ShortcutCustomizationUiState.AddShortcutDialog -> {
- Column(modifier = modifier) {
- Title(uiState.shortcutLabel)
- Description(
- text =
- stringResource(
- id = R.string.shortcut_customize_mode_add_shortcut_description
- )
- )
- PromptShortcutModifier(
- modifier =
- Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
- .width(131.dp)
- .height(48.dp),
- defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
- )
- SelectedKeyCombinationContainer(
- shouldShowError = uiState.errorMessage.isNotEmpty(),
- onKeyPress = onKeyPress,
- pressedKeys = uiState.pressedKeys,
- )
- ErrorMessageContainer(uiState.errorMessage)
- DialogButtons(
- onCancel,
- isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
- onConfirm = onConfirmSetShortcut,
- confirmButtonText =
- stringResource(
- R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
- ),
- )
- }
+ AddShortcutDialog(modifier, uiState, onKeyPress, onCancel, onConfirmSetShortcut)
}
is ShortcutCustomizationUiState.DeleteShortcutDialog -> {
- Column(modifier) {
- Title(
- title =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
- )
- )
- Description(
- text =
- stringResource(
- id = R.string.shortcut_customize_mode_remove_shortcut_description
- )
- )
- DialogButtons(
- onCancel = onCancel,
- onConfirm = onConfirmDeleteShortcut,
- confirmButtonText =
- stringResource(
- R.string.shortcut_helper_customize_dialog_remove_button_label
- ),
- )
- }
+ DeleteShortcutDialog(modifier, onCancel, onConfirmDeleteShortcut)
+ }
+ is ShortcutCustomizationUiState.ResetShortcutDialog -> {
+ ResetShortcutDialog(modifier, onCancel, onConfirmResetShortcut)
}
else -> {
- /* No-Op */
+ /* No-op */
}
}
}
@Composable
-fun DialogButtons(
+private fun AddShortcutDialog(
+ modifier: Modifier,
+ uiState: ShortcutCustomizationUiState.AddShortcutDialog,
+ onKeyPress: (KeyEvent) -> Boolean,
+ onCancel: () -> Unit,
+ onConfirmSetShortcut: () -> Unit
+){
+ Column(modifier = modifier) {
+ Title(uiState.shortcutLabel)
+ Description(
+ text =
+ stringResource(
+ id = R.string.shortcut_customize_mode_add_shortcut_description
+ )
+ )
+ PromptShortcutModifier(
+ modifier =
+ Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+ .width(131.dp)
+ .height(48.dp),
+ defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
+ )
+ SelectedKeyCombinationContainer(
+ shouldShowError = uiState.errorMessage.isNotEmpty(),
+ onKeyPress = onKeyPress,
+ pressedKeys = uiState.pressedKeys,
+ )
+ ErrorMessageContainer(uiState.errorMessage)
+ DialogButtons(
+ onCancel,
+ isConfirmButtonEnabled = uiState.pressedKeys.isNotEmpty(),
+ onConfirm = onConfirmSetShortcut,
+ confirmButtonText =
+ stringResource(
+ R.string.shortcut_helper_customize_dialog_set_shortcut_button_label
+ ),
+ )
+ }
+}
+
+@Composable
+private fun DeleteShortcutDialog(
+ modifier: Modifier,
+ onCancel: () -> Unit,
+ onConfirmDeleteShortcut: () -> Unit
+){
+ ConfirmationDialog(
+ modifier = modifier,
+ title =
+ stringResource(
+ id = R.string.shortcut_customize_mode_remove_shortcut_dialog_title
+ ),
+ description =
+ stringResource(
+ id = R.string.shortcut_customize_mode_remove_shortcut_description
+ ),
+ confirmButtonText =
+ stringResource(R.string.shortcut_helper_customize_dialog_remove_button_label),
+ onCancel = onCancel,
+ onConfirm = onConfirmDeleteShortcut,
+ )
+}
+
+@Composable
+private fun ResetShortcutDialog(
+ modifier: Modifier,
+ onCancel: () -> Unit,
+ onConfirmResetShortcut: () -> Unit
+){
+ ConfirmationDialog(
+ modifier = modifier,
+ title =
+ stringResource(
+ id = R.string.shortcut_customize_mode_reset_shortcut_dialog_title
+ ),
+ description =
+ stringResource(
+ id = R.string.shortcut_customize_mode_reset_shortcut_description
+ ),
+ confirmButtonText =
+ stringResource(R.string.shortcut_helper_customize_dialog_reset_button_label),
+ onCancel = onCancel,
+ onConfirm = onConfirmResetShortcut,
+ )
+}
+
+@Composable
+private fun ConfirmationDialog(
+ modifier: Modifier,
+ title: String,
+ description: String,
+ confirmButtonText: String,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Column(modifier) {
+ Title(title = title)
+ Description(text = description)
+ DialogButtons(
+ onCancel = onCancel,
+ onConfirm = onConfirm,
+ confirmButtonText = confirmButtonText,
+ )
+ }
+}
+
+@Composable
+private fun DialogButtons(
onCancel: () -> Unit,
isConfirmButtonEnabled: Boolean = true,
onConfirm: () -> Unit,
@@ -168,7 +228,7 @@ fun DialogButtons(
}
@Composable
-fun ErrorMessageContainer(errorMessage: String) {
+private fun ErrorMessageContainer(errorMessage: String) {
if (errorMessage.isNotEmpty()) {
Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) {
Text(
@@ -185,7 +245,7 @@ fun ErrorMessageContainer(errorMessage: String) {
}
@Composable
-fun SelectedKeyCombinationContainer(
+private fun SelectedKeyCombinationContainer(
shouldShowError: Boolean,
onKeyPress: (KeyEvent) -> Boolean,
pressedKeys: List<ShortcutKey>,
@@ -352,7 +412,7 @@ private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
}
@Composable
-fun ActionKeyText() {
+private fun ActionKeyText() {
Text(
text = "Action",
style = MaterialTheme.typography.titleMedium,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 1f37c7de1439..79293077bc4c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -471,6 +471,9 @@ private fun EndSidePanel(
is ShortcutCustomizationRequestInfo.Delete ->
onCustomizationRequested(requestInfo.copy(categoryType = category.type))
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
}
},
)
@@ -535,6 +538,9 @@ private fun SubCategoryContainerDualPane(
onCustomizationRequested(
requestInfo.copy(subCategoryLabel = subCategory.label)
)
+
+ ShortcutCustomizationRequestInfo.Reset ->
+ onCustomizationRequested(requestInfo)
}
},
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
index 990257d642ff..bfc9486552a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -23,11 +23,17 @@ sealed interface ShortcutCustomizationUiState {
val shortcutLabel: String,
val errorMessage: String = "",
val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
- val isDialogShowing: Boolean,
+ val isDialogShowing: Boolean = false,
val pressedKeys: List<ShortcutKey> = emptyList(),
) : ShortcutCustomizationUiState
- data class DeleteShortcutDialog(val isDialogShowing: Boolean) : ShortcutCustomizationUiState
+ data class DeleteShortcutDialog(
+ val isDialogShowing: Boolean = false
+ ) : ShortcutCustomizationUiState
+
+ data class ResetShortcutDialog(
+ val isDialogShowing: Boolean = false
+ ) : ShortcutCustomizationUiState
data object Inactive : ShortcutCustomizationUiState
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index b467bb481443..76a2e6095f01 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog
import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog
import com.android.systemui.res.R
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -82,6 +83,10 @@ constructor(
_shortcutCustomizationUiState.value = DeleteShortcutDialog(isDialogShowing = false)
shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
+
+ ShortcutCustomizationRequestInfo.Reset -> {
+ _shortcutCustomizationUiState.value = ResetShortcutDialog(isDialogShowing = false)
+ }
}
}
@@ -89,6 +94,7 @@ constructor(
_shortcutCustomizationUiState.update { uiState ->
(uiState as? AddShortcutDialog)?.copy(isDialogShowing = true)
?: (uiState as? DeleteShortcutDialog)?.copy(isDialogShowing = true)
+ ?: (uiState as? ResetShortcutDialog)?.copy(isDialogShowing = true)
?: uiState
}
}
@@ -134,8 +140,18 @@ constructor(
}
suspend fun deleteShortcutCurrentlyBeingCustomized() {
- val result =
- shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+ val result = shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+
+ _shortcutCustomizationUiState.update { uiState ->
+ when (result) {
+ ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
+ else -> uiState
+ }
+ }
+ }
+
+ suspend fun resetAllCustomShortcuts() {
+ val result = shortcutCustomizationInteractor.resetAllCustomShortcuts()
_shortcutCustomizationUiState.update { uiState ->
when (result) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0a4e8c660761..b1719107fae1 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -68,6 +68,7 @@ import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.window.BackEvent;
import androidx.annotation.DimenRes;
@@ -585,6 +586,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mNonLinearFactor = getDimenFloat(res,
com.android.internal.R.dimen.back_progress_non_linear_factor);
updateBackAnimationThresholds();
+ mBackgroundExecutor.execute(this::disableNavBarVirtualKeyHapticFeedback);
}
private float getDimenFloat(Resources res, @DimenRes int resId) {
@@ -1287,6 +1289,15 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
}
+ private void disableNavBarVirtualKeyHapticFeedback() {
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .setNavBarVirtualKeyHapticFeedbackEnabled(false);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to disable navigation bar button haptics: ", e);
+ }
+ }
+
public void dump(PrintWriter pw) {
pw.println("EdgeBackGestureHandler:");
pw.println(" mIsEnabled=" + mIsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index bdc58c1ceeb1..eb568f73a854 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -280,6 +280,18 @@ class ScreenRecordPermissionDialogDelegate(
private const val DELAY_MS: Long = 3000
private const val INTERVAL_MS: Long = 1000
+ private val RECORDABLE_DISPLAY_TYPES =
+ intArrayOf(
+ Display.TYPE_OVERLAY,
+ Display.TYPE_EXTERNAL,
+ Display.TYPE_INTERNAL,
+ Display.TYPE_WIFI,
+ )
+
+ private val filterDeviceTypeFlag: Boolean =
+ com.android.media.projection.flags.Flags
+ .mediaProjectionConnectedDisplayNoVirtualDevice()
+
private fun createOptionList(displayManager: DisplayManager): List<ScreenShareOption> {
if (!com.android.media.projection.flags.Flags.mediaProjectionConnectedDisplay()) {
return listOf(
@@ -302,6 +314,7 @@ class ScreenRecordPermissionDialogDelegate(
),
)
}
+
return listOf(
ScreenShareOption(
SINGLE_APP,
@@ -322,7 +335,10 @@ class ScreenRecordPermissionDialogDelegate(
),
) +
displayManager.displays
- .filter { it.displayId != Display.DEFAULT_DISPLAY }
+ .filter {
+ it.displayId != Display.DEFAULT_DISPLAY &&
+ (!filterDeviceTypeFlag || it.type in RECORDABLE_DISPLAY_TYPES)
+ }
.map {
ScreenShareOption(
ENTIRE_SCREEN,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index d210e93e36f1..c03ba0178fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -26,7 +26,12 @@ import java.io.PrintWriter;
/**
* Source of truth for keyguard state: If locked, occluded, has password, trusted etc.
+ *
+ * @deprecated this class is not supported when KEYGUARD_WM_STATE_REFACTOR is enabled.
+ * Use {@link com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor}
+ * or {@link com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor} instead.
*/
+@Deprecated
public interface KeyguardStateController extends CallbackController<Callback>, Dumpable {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index 772ae7736cad..c0c525bcb37d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.dialog.sliders.dagger
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
import dagger.BindsInstance
@@ -34,6 +35,8 @@ interface VolumeDialogSliderComponent {
fun sliderTouchesViewBinder(): VolumeDialogSliderTouchesViewBinder
+ fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
+
@Subcomponent.Factory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
new file mode 100644
index 000000000000..5a7fbc6341f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderHapticsViewBinder.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.ui
+
+import android.view.View
+import com.android.systemui.haptics.slider.HapticSlider
+import com.android.systemui.haptics.slider.HapticSliderPlugin
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.shared.model.SliderInputEvent
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
+import com.google.android.material.slider.Slider
+import com.google.android.msdl.domain.MSDLPlayer
+import javax.inject.Inject
+import kotlin.math.roundToInt
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@VolumeDialogSliderScope
+class VolumeDialogSliderHapticsViewBinder
+@Inject
+constructor(
+ private val inputEventsViewModel: VolumeDialogSliderInputEventsViewModel,
+ private val vibratorHelper: VibratorHelper,
+ private val msdlPlayer: MSDLPlayer,
+ private val systemClock: SystemClock,
+) {
+
+ fun CoroutineScope.bind(view: View) {
+ val sliderView = view.requireViewById<Slider>(R.id.volume_dialog_slider)
+ val hapticSliderPlugin =
+ HapticSliderPlugin(
+ slider = HapticSlider.Slider(sliderView),
+ vibratorHelper = vibratorHelper,
+ msdlPlayer = msdlPlayer,
+ systemClock = systemClock,
+ )
+ hapticSliderPlugin.startInScope(this)
+
+ sliderView.addOnChangeListener { _, value, fromUser ->
+ hapticSliderPlugin.onProgressChanged(value.roundToInt(), fromUser)
+ }
+ sliderView.addOnSliderTouchListener(
+ object : Slider.OnSliderTouchListener {
+
+ override fun onStartTrackingTouch(slider: Slider) {
+ hapticSliderPlugin.onStartTrackingTouch()
+ }
+
+ override fun onStopTrackingTouch(slider: Slider) {
+ hapticSliderPlugin.onStopTrackingTouch()
+ }
+ }
+ )
+
+ inputEventsViewModel.event
+ .onEach {
+ when (it) {
+ is SliderInputEvent.Button -> hapticSliderPlugin.onKeyDown()
+ is SliderInputEvent.Touch -> hapticSliderPlugin.onTouchEvent(it.event)
+ }
+ }
+ .launchIn(this)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
index e0336243b327..4ecac7a81893 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
@@ -20,14 +20,14 @@ import android.annotation.SuppressLint
import android.view.View
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderTouchesViewModel
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
import com.google.android.material.slider.Slider
import javax.inject.Inject
@VolumeDialogSliderScope
class VolumeDialogSliderTouchesViewBinder
@Inject
-constructor(private val viewModel: VolumeDialogSliderTouchesViewModel) {
+constructor(private val viewModel: VolumeDialogSliderInputEventsViewModel) {
@SuppressLint("ClickableViewAccessibility")
fun bind(view: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index f9334dfc7ba2..e52bad9c39bf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -57,7 +57,6 @@ constructor(
}
private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
- slider.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> }
with(slider) {
valueFrom = levelMin.toFloat()
valueTo = levelMax.toFloat()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 242845a47f29..c9b525930ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -62,6 +62,7 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) {
) {
with(component.sliderViewBinder()) { bind(sliderContainer) }
with(component.sliderTouchesViewBinder()) { bind(sliderContainer) }
+ with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
index 9126f45fe32a..755776ac9723 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderTouchesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderInputEventsViewModel.kt
@@ -17,14 +17,25 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInputEventsInteractor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.stateIn
@VolumeDialogSliderScope
-class VolumeDialogSliderTouchesViewModel
+class VolumeDialogSliderInputEventsViewModel
@Inject
-constructor(private val interactor: VolumeDialogSliderInputEventsInteractor) {
+constructor(
+ @VolumeDialog private val coroutineScope: CoroutineScope,
+ private val interactor: VolumeDialogSliderInputEventsInteractor,
+) {
+
+ val event =
+ interactor.event.stateIn(coroutineScope, SharingStarted.Eagerly, null).filterNotNull()
fun onTouchEvent(event: MotionEvent) {
interactor.onTouchEvent(event)
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a0b989b44f4f..ad87ceaf6f38 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -86,10 +86,17 @@ flag {
}
flag {
- name: "enable_hardware_shortcut_disables_warning"
- namespace: "accessibility"
- description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
- bug: "287065325"
+ name: "enable_hardware_shortcut_disables_warning"
+ namespace: "accessibility"
+ description: "When the user purposely enables the hardware shortcut, preemptively disables the first-time warning message."
+ bug: "287065325"
+}
+
+flag {
+ name: "enable_low_vision_hats"
+ namespace: "accessibility"
+ description: "Use HaTS for low vision feedback."
+ bug: "380346799"
}
flag {
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index 5d2ef770b96b..5e1b1473d233 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -23,6 +23,16 @@ flag {
}
flag {
+ name: "relayout_fix"
+ namespace: "autofill"
+ description: "Fixing relayout issue. Guarding enabling device config flags"
+ bug: "381226145"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "include_invisible_view_group_in_assist_structure"
namespace: "autofill"
description: "Mitigation for autofill providers miscalculating view visibility"
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 57036335bb75..80e0e5dc6e1a 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "autofill"
description: "Guards against new metrics definitions introduced in W"
bug: "342676602"
+ is_exported: true
}
flag {
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 9326ea89e21b..b4adad2c8bef 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -68,6 +68,7 @@ flag {
"B&R operations in certain cases."
bug: "376661510"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 3976d01d806d..123b7dfbf843 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -9,6 +9,7 @@ flag {
description: "Allows querying of AOD availability"
bug: "324046664"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -267,6 +268,7 @@ flag {
description: "Feature flag for an API to get the highest defined HDR/SDR ratio for a display."
bug: "335181559"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -429,6 +431,7 @@ flag {
description: "Flag for an API to get whether display supports ARR or not"
bug: "361433651"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -445,6 +448,7 @@ flag {
description: "Flag for an API to get suggested frame rates"
bug: "361433796"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -453,6 +457,7 @@ flag {
description: "Feature flag for an API to let the apps subscribe to a specific property change of the Display."
bug: "372700957"
is_fixed_read_only: true
+ is_exported: true
}
flag {
@@ -461,6 +466,7 @@ flag {
description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
bug: "365163968"
is_fixed_read_only: true
+ is_exported: true
}
flag {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 2e167efc4d81..6053557f575c 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -108,6 +108,7 @@ import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.location.eventlog.LocationEventLog;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.geofence.GeofenceManager;
import com.android.server.location.geofence.GeofenceProxy;
import com.android.server.location.gnss.GnssConfiguration;
@@ -147,6 +148,7 @@ import com.android.server.location.provider.PassiveLocationProviderManager;
import com.android.server.location.provider.StationaryThrottlingLocationProvider;
import com.android.server.location.provider.proxy.ProxyGeocodeProvider;
import com.android.server.location.provider.proxy.ProxyLocationProvider;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
@@ -260,6 +262,11 @@ public class LocationManagerService extends ILocationManager.Stub implements
private volatile @Nullable GnssManagerService mGnssManagerService = null;
private ProxyGeocodeProvider mGeocodeProvider;
+ private @Nullable ProxyPopulationDensityProvider mPopulationDensityProvider = null;
+
+ // A cache for population density lookups. Used if density-based coarse locations are enabled.
+ private @Nullable LocationFudgerCache mLocationFudgerCache = null;
+
private final Object mDeprecatedGnssBatchingLock = new Object();
@GuardedBy("mDeprecatedGnssBatchingLock")
private @Nullable ILocationListener mDeprecatedGnssBatchingListener;
@@ -392,6 +399,25 @@ public class LocationManagerService extends ILocationManager.Stub implements
}
}
+ @VisibleForTesting
+ protected void setProxyPopulationDensityProvider(ProxyPopulationDensityProvider provider) {
+ if (Flags.populationDensityProvider()) {
+ mPopulationDensityProvider = provider;
+ }
+ }
+
+ @VisibleForTesting
+ protected void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudgerCache = cache;
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.setLocationFudgerCache(cache);
+ }
+ }
+
private void removeLocationProviderManager(LocationProviderManager manager) {
synchronized (mProviderManagers) {
boolean removed = mProviderManagers.remove(manager);
@@ -510,6 +536,17 @@ public class LocationManagerService extends ILocationManager.Stub implements
Log.e(TAG, "no geocoder provider found");
}
+ if (Flags.populationDensityProvider()) {
+ setProxyPopulationDensityProvider(
+ ProxyPopulationDensityProvider.createAndRegister(mContext));
+ if (mPopulationDensityProvider == null) {
+ Log.e(TAG, "no population density provider found");
+ }
+ }
+ if (mPopulationDensityProvider != null && Flags.densityBasedCoarseLocations()) {
+ setLocationFudgerCache(new LocationFudgerCache(mPopulationDensityProvider));
+ }
+
// bind to hardware activity recognition
HardwareActivityRecognitionProxy hardwareActivityRecognitionProxy =
HardwareActivityRecognitionProxy.createAndRegister(mContext);
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudger.java b/services/core/java/com/android/server/location/fudger/LocationFudger.java
index 88a269706470..0da1514872d6 100644
--- a/services/core/java/com/android/server/location/fudger/LocationFudger.java
+++ b/services/core/java/com/android/server/location/fudger/LocationFudger.java
@@ -16,13 +16,16 @@
package com.android.server.location.fudger;
+import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.location.Location;
import android.location.LocationResult;
+import android.location.flags.Flags;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.location.geometry.S2CellIdUtils;
import java.security.SecureRandom;
import java.time.Clock;
@@ -83,6 +86,9 @@ public class LocationFudger {
@GuardedBy("this")
@Nullable private LocationResult mCachedCoarseLocationResult;
+ @GuardedBy("this")
+ @Nullable private LocationFudgerCache mLocationFudgerCache = null;
+
public LocationFudger(float accuracyM) {
this(accuracyM, SystemClock.elapsedRealtimeClock(), new SecureRandom());
}
@@ -97,6 +103,16 @@ public class LocationFudger {
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ synchronized (this) {
+ mLocationFudgerCache = cache;
+ }
+ }
+
+ /**
* Resets the random offsets completely.
*/
public void resetOffsets() {
@@ -162,16 +178,34 @@ public class LocationFudger {
longitude += wrapLongitude(metersToDegreesLongitude(mLongitudeOffsetM, latitude));
latitude += wrapLatitude(metersToDegreesLatitude(mLatitudeOffsetM));
- // quantize location by snapping to a grid. this is the primary means of obfuscation. it
- // gives nice consistent results and is very effective at hiding the true location (as long
- // as you are not sitting on a grid boundary, which the random offsets mitigate).
- //
- // note that we quantize the latitude first, since the longitude quantization depends on the
- // latitude value and so leaks information about the latitude
- double latGranularity = metersToDegreesLatitude(mAccuracyM);
- latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
- double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
- longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ // We copy a reference to the cache, so even if mLocationFudgerCache is concurrently set
+ // to null, we can continue executing the condition below.
+ LocationFudgerCache cacheCopy = null;
+ synchronized (this) {
+ cacheCopy = mLocationFudgerCache;
+ }
+
+ // TODO(b/381204398): To ensure a safe rollout, two algorithms co-exist. The first is the
+ // new density-based algorithm, while the second is the traditional coarsening algorithm.
+ // Once rollout is done, clean up the unused algorithm.
+ if (Flags.densityBasedCoarseLocations() && cacheCopy != null
+ && cacheCopy.hasDefaultValue()) {
+ int level = cacheCopy.getCoarseningLevel(latitude, longitude);
+ double[] center = snapToCenterOfS2Cell(latitude, longitude, level);
+ latitude = center[S2CellIdUtils.LAT_INDEX];
+ longitude = center[S2CellIdUtils.LNG_INDEX];
+ } else {
+ // quantize location by snapping to a grid. this is the primary means of obfuscation. it
+ // gives nice consistent results and is very effective at hiding the true location (as
+ // long as you are not sitting on a grid boundary, which the random offsets mitigate).
+ //
+ // note that we quantize the latitude first, since the longitude quantization depends on
+ // the latitude value and so leaks information about the latitude
+ double latGranularity = metersToDegreesLatitude(mAccuracyM);
+ latitude = wrapLatitude(Math.round(latitude / latGranularity) * latGranularity);
+ double lonGranularity = metersToDegreesLongitude(mAccuracyM, latitude);
+ longitude = wrapLongitude(Math.round(longitude / lonGranularity) * lonGranularity);
+ }
coarse.setLatitude(latitude);
coarse.setLongitude(longitude);
@@ -185,6 +219,15 @@ public class LocationFudger {
return coarse;
}
+ @VisibleForTesting
+ protected double[] snapToCenterOfS2Cell(double latDegrees, double lngDegrees, int level) {
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latDegrees, lngDegrees);
+ long coarsenedCell = S2CellIdUtils.getParent(leafCell, level);
+ double[] center = new double[] {0.0, 0.0};
+ S2CellIdUtils.toLatLngDegrees(coarsenedCell, center);
+ return center;
+ }
+
/**
* Update the random offsets over time.
*
diff --git a/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
new file mode 100644
index 000000000000..3670c1f5c51b
--- /dev/null
+++ b/services/core/java/com/android/server/location/fudger/LocationFudgerCache.java
@@ -0,0 +1,199 @@
+/*
+ * 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.fudger;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import java.util.Objects;
+
+/**
+ * A cache for returning the coarsening level to be used. The coarsening level depends on the user
+ * location. If the cache contains the requested latitude/longitude, the s2 level of the cached
+ * cell id is returned. If not, a default value is returned.
+ * This class has a {@link ProxyPopulationDensityProvider} used to refresh the cache.
+ * This cache exists because {@link ProxyPopulationDensityProvider} must be queried asynchronously,
+ * whereas a synchronous answer is needed.
+ * The cache is first-in, first-out, and has a fixed size. Cache entries are valid until evicted by
+ * another value.
+ */
+@FlaggedApi(Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+public class LocationFudgerCache {
+
+ // The maximum number of S2 cell ids stored in the cache.
+ // Each cell id is a long, so the memory requirement is 8*MAX_CACHE_SIZE bytes.
+ protected static final int MAX_CACHE_SIZE = 20;
+
+ private final Object mLock = new Object();
+
+ // mCache is a circular buffer of size MAX_CACHE_SIZE. The next position to be written to is
+ // mPosInCache. Initially, the cache is filled with INVALID_CELL_IDs.
+ @GuardedBy("mLock")
+ private final long[] mCache = new long[MAX_CACHE_SIZE];
+
+ @GuardedBy("mLock")
+ private int mPosInCache = 0;
+
+ @GuardedBy("mLock")
+ private int mCacheSize = 0;
+
+ // The S2 level to coarsen to, if the cache doesn't contain a better answer.
+ // Updated concurrently by callbacks.
+ @GuardedBy("mLock")
+ private Integer mDefaultCoarseningLevel = null;
+
+ // The provider that asynchronously provides what is stored in the cache.
+ private final ProxyPopulationDensityProvider mPopulationDensityProvider;
+
+ private static String sTAG = "LocationFudgerCache";
+
+ public LocationFudgerCache(@NonNull ProxyPopulationDensityProvider provider) {
+ mPopulationDensityProvider = Objects.requireNonNull(provider);
+
+ asyncFetchDefaultCoarseningLevel();
+ }
+
+ /** Returns true if the cache has successfully received a default value from the provider. */
+ public boolean hasDefaultValue() {
+ synchronized (mLock) {
+ return (mDefaultCoarseningLevel != null);
+ }
+ }
+
+ /**
+ * Returns the S2 level to which the provided location should be coarsened.
+ * The answer comes from the cache if available, otherwise the default value is returned.
+ */
+ public int getCoarseningLevel(double latitudeDegrees, double longitudeDegrees) {
+ // If we still haven't received the default level from the provider, try fetching it again.
+ // The answer wouldn't come in time, but it will be used for the following queries.
+ if (!hasDefaultValue()) {
+ asyncFetchDefaultCoarseningLevel();
+ }
+ Long s2CellId = readCacheForLatLng(latitudeDegrees, longitudeDegrees);
+ if (s2CellId == null) {
+ // Asynchronously queries the density from the provider. The answer won't come in time,
+ // but it will update the cache for the following queries.
+ refreshCache(latitudeDegrees, longitudeDegrees);
+
+ return getDefaultCoarseningLevel();
+ }
+ return S2CellIdUtils.getLevel(s2CellId);
+ }
+
+ /**
+ * If the cache contains the current location, returns the corresponding S2 cell id.
+ * Otherwise, returns null.
+ */
+ @Nullable
+ private Long readCacheForLatLng(double latDegrees, double lngDegrees) {
+ synchronized (mLock) {
+ for (int i = 0; i < mCacheSize; i++) {
+ if (S2CellIdUtils.containsLatLngDegrees(mCache[i], latDegrees, lngDegrees)) {
+ return mCache[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ /** Adds the provided s2 cell id to the cache. This might evict other values from the cache. */
+ public void addToCache(long s2CellId) {
+ addToCache(new long[] {s2CellId});
+ }
+
+ /**
+ * Adds the provided s2 cell ids to the cache. This might evict other values from the cache.
+ * If more than MAX_CACHE_SIZE elements are provided, only the first elements are copied.
+ * The first element of the input is added last into the FIFO cache, so it gets evicted last.
+ */
+ public void addToCache(long[] s2CellIds) {
+ synchronized (mLock) {
+ // Only copy up to MAX_CACHE_SIZE elements
+ int end = Math.min(s2CellIds.length, MAX_CACHE_SIZE);
+ mCacheSize = Math.min(mCacheSize + end, MAX_CACHE_SIZE);
+
+ // Add in reverse so the first cell of s2CellIds is the last evicted
+ for (int i = end - 1; i >= 0; i--) {
+ mCache[mPosInCache] = s2CellIds[i];
+ mPosInCache = (mPosInCache + 1) % MAX_CACHE_SIZE;
+ }
+ }
+ }
+
+ /**
+ * Queries the population density provider for the default coarsening level (to be used if the
+ * cache doesn't contain a better answer), and updates mDefaultCoarseningLevel with the answer.
+ */
+ private void asyncFetchDefaultCoarseningLevel() {
+ IS2LevelCallback callback = new IS2LevelCallback.Stub() {
+ @Override
+ public void onResult(int s2level) {
+ synchronized (mLock) {
+ mDefaultCoarseningLevel = Integer.valueOf(s2level);
+ }
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get default population density");
+ }
+ };
+ mPopulationDensityProvider.getDefaultCoarseningLevel(callback);
+ }
+
+ /**
+ * Queries the population density provider and store the result in the cache.
+ */
+ private void refreshCache(double latitude, double longitude) {
+ IS2CellIdsCallback callback = new IS2CellIdsCallback.Stub() {
+ @Override
+ public void onResult(long[] s2CellIds) {
+ addToCache(s2CellIds);
+ }
+
+ @Override
+ public void onError() {
+ Log.e(sTAG, "could not get population density");
+ }
+ };
+ mPopulationDensityProvider.getCoarsenedS2Cell(latitude, longitude, callback);
+ }
+
+ /**
+ * Returns the default S2 level to coarsen to. This should be used if the cache
+ * does not provide a better answer.
+ */
+ private int getDefaultCoarseningLevel() {
+ synchronized (mLock) {
+ // The minimum valid level is 0.
+ if (mDefaultCoarseningLevel == null) {
+ return 0;
+ }
+ return mDefaultCoarseningLevel;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 4a9bf88aae33..a8c90100ebf0 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -48,6 +48,7 @@ import static com.android.server.location.eventlog.LocationEventLog.EVENT_LOG;
import static java.lang.Math.max;
import static java.lang.Math.min;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
@@ -105,6 +106,7 @@ import com.android.server.LocalServices;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.fudger.LocationFudger;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.AlarmHelper;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.AppForegroundHelper.AppForegroundListener;
@@ -1663,6 +1665,18 @@ public class LocationProviderManager extends
}
/**
+ * Provides the optional {@link LocationFudgerCache} for coarsening based on population density.
+ */
+ @FlaggedApi(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+ public void setLocationFudgerCache(LocationFudgerCache cache) {
+ if (!Flags.densityBasedCoarseLocations()) {
+ return;
+ }
+
+ mLocationFudger.setLocationFudgerCache(cache);
+ }
+
+ /**
* Returns true if this provider is visible to the current caller (whether called from a binder
* thread or not). If a provider isn't visible, then all APIs return the same data they would if
* the provider didn't exist (i.e. the caller can't see or use the provider).
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
new file mode 100644
index 000000000000..b0a0f0b0c83b
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyPopulationDensityProvider.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider.proxy;
+
+import static android.location.provider.PopulationDensityProviderBase.ACTION_POPULATION_DENSITY_PROVIDER;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.provider.IPopulationDensityProvider;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
+
+/**
+ * Proxy for IPopulationDensityProvider implementations.
+ */
+public class ProxyPopulationDensityProvider {
+
+ public static final String TAG = "ProxyPopulationDensityProvider";
+
+ final ServiceWatcher mServiceWatcher;
+
+ /**
+ * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+ * null.
+ */
+ @Nullable
+ public static ProxyPopulationDensityProvider createAndRegister(Context context) {
+ ProxyPopulationDensityProvider proxy = new ProxyPopulationDensityProvider(context);
+ if (proxy.register()) {
+ return proxy;
+ } else {
+ return null;
+ }
+ }
+
+ private ProxyPopulationDensityProvider(Context context) {
+ mServiceWatcher = ServiceWatcher.create(
+ context,
+ "PopulationDensityProxy",
+ CurrentUserServiceSupplier.createFromConfig(
+ context,
+ ACTION_POPULATION_DENSITY_PROVIDER,
+ com.android.internal.R.bool.config_enablePopulationDensityProviderOverlay,
+ com.android.internal.R.string.config_populationDensityProviderPackageName),
+ null);
+ }
+
+ private boolean register() {
+ boolean resolves = mServiceWatcher.checkServiceResolves();
+ if (resolves) {
+ mServiceWatcher.register();
+ }
+ return resolves;
+ }
+
+ /** Gets the default coarsening level. */
+ public void getDefaultCoarseningLevel(IS2LevelCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getDefaultCoarseningLevel(callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying default coarsening level");
+ }
+ }
+ });
+ }
+
+
+ /** Gets the population density at the requested location. */
+ public void getCoarsenedS2Cell(double latitudeDegrees, double longitudeDegrees,
+ IS2CellIdsCallback callback) {
+ mServiceWatcher.runOnBinder(
+ new ServiceWatcher.BinderOperation() {
+ @Override
+ public void run(IBinder binder) throws RemoteException {
+ IPopulationDensityProvider.Stub.asInterface(binder)
+ .getCoarsenedS2Cell(latitudeDegrees, longitudeDegrees, callback);
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ try {
+ callback.onError();
+ } catch (RemoteException e) {
+ Log.w(TAG, "remote exception while querying coarsened S2 cell");
+ }
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index ceb931400d48..516b002885af 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -2329,7 +2329,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
- mInstallDependencyHelper.notifySessionComplete(session.sessionId, success);
+ if (Flags.sdkDependencyInstaller()) {
+ mInstallDependencyHelper.notifySessionComplete(
+ session.sessionId, success);
+ }
final File appIconFile = buildAppIconFile(session.sessionId);
if (appIconFile.exists()) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index e49dc8250bc7..976999cf6ae0 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -426,6 +426,7 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
private static final int TRON_COMPILATION_REASON_PREBUILT = 23;
private static final int TRON_COMPILATION_REASON_VDEX = 24;
private static final int TRON_COMPILATION_REASON_BOOT_AFTER_MAINLINE_UPDATE = 25;
+ private static final int TRON_COMPILATION_REASON_CLOUD = 26;
// The annotation to add as a suffix to the compilation reason when dexopt was
// performed with dex metadata.
@@ -460,6 +461,8 @@ public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
return TRON_COMPILATION_REASON_INSTALL_BULK_DOWNGRADED;
case "install-bulk-secondary-downgraded" :
return TRON_COMPILATION_REASON_INSTALL_BULK_SECONDARY_DOWNGRADED;
+ case "cloud":
+ return TRON_COMPILATION_REASON_CLOUD;
// These are special markers for dex metadata installation that do not
// have an equivalent as a system property.
case "install" + DEXOPT_REASON_WITH_DEX_METADATA_ANNOTATION :
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index ce6f57fec0a7..5e048810cc97 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -29,6 +29,7 @@ flag {
namespace: "backstage_power"
description: "Feature flag for streamlined connectivity battery stats"
bug: "323970018"
+ is_exported: true
}
flag {
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
index 9e75cf2fc3f3..15c3099511f9 100644
--- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -86,6 +86,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@GuardedBy("mLock")
private Status mEndStatusRequest;
@GuardedBy("mLock")
+ private boolean mEndedByVendor;
+ @GuardedBy("mLock")
private long mStartTime; // for debugging
@GuardedBy("mLock")
private long mEndUptime;
@@ -119,14 +121,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub
public void finishSession() {
// Do not abort session in HAL, wait for ongoing vibration requests to complete.
// This might take a while to end the session, but it can be aborted by cancelSession.
- requestEndSession(Status.FINISHED, /* shouldAbort= */ false);
+ requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true);
}
@Override
public void cancelSession() {
// Always abort session in HAL while cancelling it.
// This might be triggered after finishSession was already called.
- requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true);
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
}
@Override
@@ -158,7 +161,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
public DebugInfo getDebugInfo() {
synchronized (mLock) {
return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
- mEndUptime, mEndTime, mVibrations);
+ mEndUptime, mEndTime, mEndedByVendor, mVibrations);
}
}
@@ -172,13 +175,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@Override
public void onCancel() {
Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
- requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false);
+ requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true,
+ /* isVendorRequest= */ true);
}
@Override
public void binderDied() {
Slog.d(TAG, "Binder died, cancelling vibration session...");
- requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+ requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true,
+ /* isVendorRequest= */ false);
}
@Override
@@ -207,7 +212,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub
// All requests to end a session should abort it to stop ongoing vibrations, even if
// immediate flag is false. Only the #finishSession API will not abort and wait for
// session vibrations to complete, which might take a long time.
- requestEndSession(status, /* shouldAbort= */ true);
+ requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false);
}
@Override
@@ -224,7 +229,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub
public void notifySessionCallback() {
synchronized (mLock) {
// If end was not requested then the HAL has cancelled the session.
- maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
+ maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON,
+ /* isVendorRequest= */ false);
maybeSetStatusToRequestedLocked();
clearVibrationConductor();
}
@@ -335,10 +341,10 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
}
- private void requestEndSession(Status status, boolean shouldAbort) {
+ private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) {
boolean shouldTriggerSessionHook = false;
synchronized (mLock) {
- maybeSetEndRequestLocked(status);
+ maybeSetEndRequestLocked(status, isVendorRequest);
if (isStarted()) {
// Always trigger session hook after it has started, in case new request aborts an
// already finishing session. Wait for HAL callback before actually ending here.
@@ -354,12 +360,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub
}
@GuardedBy("mLock")
- private void maybeSetEndRequestLocked(Status status) {
+ private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) {
if (mEndStatusRequest != null) {
// End already requested, keep first requested status and time.
return;
}
mEndStatusRequest = status;
+ mEndedByVendor = isVendorRequest;
mEndTime = System.currentTimeMillis();
mEndUptime = SystemClock.uptimeMillis();
if (mConductor != null) {
@@ -442,15 +449,18 @@ final class VendorVibrationSession extends IVibrationSession.Stub
private final long mStartTime;
private final long mEndTime;
private final long mDurationMs;
+ private final boolean mEndedByVendor;
DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
- long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) {
+ long startTime, long endUptime, long endTime, boolean endedByVendor,
+ List<DebugInfo> vibrations) {
mStatus = status;
mCallerInfo = callerInfo;
mCreateUptime = createUptime;
mCreateTime = createTime;
mStartTime = startTime;
mEndTime = endTime;
+ mEndedByVendor = endedByVendor;
mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations);
}
@@ -478,6 +488,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub
@Override
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ if (mStartTime > 0) {
+ // Only log sessions that have started.
+ statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid);
+ statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid,
+ mVibrations.size());
+ if (!mEndedByVendor) {
+ statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid);
+ }
+ }
for (DebugInfo vibration : mVibrations) {
vibration.logMetrics(statsLogger);
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 27f92b2080e6..2bf44981e6d5 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -25,6 +25,7 @@ import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
@@ -211,6 +212,11 @@ abstract class Vibration {
public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
statsLogger.writeVibrationReportedAsync(mStatsInfo);
+ if (Flags.vendorVibrationEffects()) {
+ // Log effect as it was originally requested.
+ statsLogger.logVibrationCountAndSizeIfVendorEffect(mCallerInfo.uid,
+ mOriginalEffect != null ? mOriginalEffect : mPlayedEffect);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index e9c38940601c..08da43d8f0e0 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -16,8 +16,12 @@
package com.android.server.vibrator;
+import android.annotation.Nullable;
+import android.os.CombinedVibration;
import android.os.Handler;
+import android.os.Parcel;
import android.os.SystemClock;
+import android.os.VibrationEffect;
import android.util.Slog;
import android.view.HapticFeedbackConstants;
@@ -58,6 +62,16 @@ public class VibratorFrameworkStatsLogger {
"vibrator.value_vibration_adaptive_haptic_scale",
new Histogram.UniformOptions(20, 0, 2));
+ // Sizes in [1KB, ~4.5MB) defined by scaled buckets.
+ private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_effect_size",
+ new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f));
+
+ // Session vibration count in [0, ~840) defined by scaled buckets.
+ private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram(
+ "vibrator.value_vibration_vendor_session_vibrations",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
private final Object mLock = new Object();
private final Handler mHandler;
private final long mVibrationReportedLogIntervalMillis;
@@ -201,4 +215,88 @@ public class VibratorFrameworkStatsLogger {
Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid);
}
}
+
+ /** Logs when a vendor vibration session successfully started. */
+ public void logVibrationVendorSessionStarted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid);
+ }
+
+ /**
+ * Logs when a vendor vibration session is interrupted by the platform.
+ *
+ * <p>A vendor session is interrupted if it has successfully started and its end was not
+ * requested by the vendor. This could be the vibrator service interrupting an ongoing session,
+ * the vibrator HAL triggering the session completed callback early.
+ */
+ public void logVibrationVendorSessionInterrupted(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid);
+ }
+
+ /** Logs the number of vibrations requested for a single vendor vibration session. */
+ public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) {
+ sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount);
+ }
+
+ /**
+ * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}.
+ *
+ * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the
+ * {@link VibrationEffect.VendorEffect#getVendorData()} it holds.
+ */
+ public void logVibrationCountAndSizeIfVendorEffect(int uid,
+ @Nullable CombinedVibration vibration) {
+ if (vibration == null) {
+ return;
+ }
+ boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration);
+ if (hasVendorEffects) {
+ // Increment CombinedVibration with one or more vendor effects only once.
+ Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid);
+ }
+ }
+
+ private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) {
+ if (vibration instanceof CombinedVibration.Mono mono) {
+ if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ return true;
+ }
+ return false;
+ }
+ if (vibration instanceof CombinedVibration.Stereo stereo) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < stereo.getEffects().size(); i++) {
+ if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) {
+ logVibrationVendorEffectSize(uid, effect);
+ hasVendorEffects = true;
+ }
+ }
+ return hasVendorEffects;
+ }
+ if (vibration instanceof CombinedVibration.Sequential sequential) {
+ boolean hasVendorEffects = false;
+ for (int i = 0; i < sequential.getEffects().size(); i++) {
+ hasVendorEffects |= logVibrationSizeOfVendorEffects(uid,
+ sequential.getEffects().get(i));
+ }
+ return hasVendorEffects;
+ }
+ // Unknown combined vibration, skip metrics.
+ return false;
+ }
+
+ private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) {
+ int dataSize;
+ Parcel vendorData = Parcel.obtain();
+ try {
+ // Measure data size as it'll be sent to the HAL via binder, not the serialization size.
+ // PersistableBundle creates an XML representation for the data in writeToStream, so it
+ // might be larger than the actual data that is transferred between processes.
+ effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
+ dataSize = vendorData.dataSize();
+ } finally {
+ vendorData.recycle();
+ }
+ sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize);
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index cc163db4dc36..ae726c15ed79 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1197,7 +1197,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
SystemClock.uptimeMillis(), System.currentTimeMillis(),
/* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0,
- /* vibrations= */ null));
+ /* endedByVendor= */ false, /* vibrations= */ null));
}
private void logAndRecordVibration(DebugInfo info) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 70a8f563275f..a077a0b9a2ca 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2054,6 +2054,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
break;
}
}
+ long timeRemaining = endTime - System.currentTimeMillis();
+ mWindowManager.mSnapshotController.mTaskSnapshotController.waitFlush(timeRemaining);
// Force checkReadyForSleep to complete.
checkReadyForSleepLocked(false /* allowDelay */);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index fc08a91278b8..bccf6b209a54 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4267,7 +4267,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
}
final int imePolicy = mWmService.mDisplayWindowSettings.getImePolicyLocked(this);
- if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY && forceDesktopMode()) {
+ if (imePolicy == DISPLAY_IME_POLICY_FALLBACK_DISPLAY
+ && isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
// If the display has not explicitly requested for the IME to be hidden then it shall
// show the IME locally.
return DISPLAY_IME_POLICY_LOCAL;
@@ -4275,10 +4276,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return imePolicy;
}
- boolean forceDesktopMode() {
- return mWmService.mForceDesktopModeOnExternalDisplays && !isDefaultDisplay && !isPrivate();
- }
-
/** @see WindowManagerInternal#onToggleImeRequested */
void onShowImeRequested() {
if (mInputMethodWindow == null) {
@@ -4871,7 +4868,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** @return {@code true} if there is window to wait before enabling the screen. */
boolean shouldWaitForSystemDecorWindowsOnBoot() {
- if (!isDefaultDisplay && !supportsSystemDecorations()) {
+ if (!isDefaultDisplay && !isSystemDecorationsSupported()) {
// Nothing to wait because the secondary display doesn't support system decorations,
// there is no wallpaper, keyguard (status bar) or application (home) window to show
// during booting.
@@ -5750,22 +5747,48 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* @see Display#FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
*/
- boolean supportsSystemDecorations() {
- boolean forceDesktopModeOnDisplay = forceDesktopMode();
-
- if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
- // System decorations should not be forced on a rear display due to security policies.
- forceDesktopModeOnDisplay =
- forceDesktopModeOnDisplay && ((mDisplay.getFlags() & Display.FLAG_REAR) == 0);
+ boolean isSystemDecorationsSupported() {
+ if (mDisplayId == mWmService.mVr2dDisplayId) {
+ // VR virtual display will be used to run and render 2D app within a VR experience.
+ return false;
+ }
+ if (!isTrusted()) {
+ // Do not show system decorations on untrusted virtual display.
+ return false;
}
+ if (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
+ || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+ // This display is configured to show system decorations.
+ return true;
+ }
+ if (isPublicSecondaryDisplayWithDesktopModeForceEnabled()) {
+ if (com.android.window.flags.Flags.rearDisplayDisableForceDesktopSystemDecorations()) {
+ // System decorations should not be forced on a rear display due to security
+ // policies.
+ return (mDisplay.getFlags() & Display.FLAG_REAR) == 0;
+ }
+ // If the display is forced to desktop mode, treat it the same as it is configured to
+ // show system decorations.
+ return true;
+ }
+ return false;
+ }
- return (mWmService.mDisplayWindowSettings.shouldShowSystemDecorsLocked(this)
- || (mDisplay.getFlags() & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0
- || forceDesktopModeOnDisplay)
- // VR virtual display will be used to run and render 2D app within a VR experience.
- && mDisplayId != mWmService.mVr2dDisplayId
- // Do not show system decorations on untrusted virtual display.
- && isTrusted();
+ /**
+ * This is the development option to force enable desktop mode on all secondary public displays
+ * that are not owned by a virtual device.
+ * When this is enabled, it also force enable system decorations on those displays.
+ *
+ * If we need a per-display config to enable desktop mode for production, that config should
+ * also check {@link #isSystemDecorationsSupported()} to avoid breaking any security policy.
+ */
+ boolean isPublicSecondaryDisplayWithDesktopModeForceEnabled() {
+ if (!mWmService.mForceDesktopModeOnExternalDisplays || isDefaultDisplay || isPrivate()) {
+ return false;
+ }
+ // Desktop mode is not supported on virtual devices.
+ int deviceId = mRootWindowContainer.mTaskSupervisor.getDeviceIdForDisplayId(mDisplayId);
+ return deviceId == Context.DEVICE_ID_DEFAULT;
}
/**
@@ -5776,7 +5799,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
boolean isHomeSupported() {
return (mWmService.mDisplayWindowSettings.isHomeSupportedLocked(this) && isTrusted())
- || supportsSystemDecorations();
+ || isSystemDecorationsSupported();
}
/**
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 76e8a70768c1..659bb6784c89 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -659,7 +659,7 @@ public class DisplayPolicy {
}
} else {
mHasStatusBar = false;
- mHasNavigationBar = mDisplayContent.supportsSystemDecorations();
+ mHasNavigationBar = mDisplayContent.isSystemDecorationsSupported();
}
mRefreshRatePolicy = new RefreshRatePolicy(mService,
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index df209ff4cf50..f53bc700de05 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -295,7 +295,7 @@ public class DisplayRotation {
&& mDeviceStateController
.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
mDisplayRotationCoordinator.setDefaultDisplayRotationChangedCallback(
- mDefaultDisplayRotationChangedCallback);
+ displayContent.getDisplayId(), mDefaultDisplayRotationChangedCallback);
}
if (isDefaultDisplay) {
@@ -445,7 +445,8 @@ public class DisplayRotation {
final boolean isTv = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_LEANBACK);
mDefaultFixedToUserRotation =
- (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()
+ (isCar || isTv || mService.mIsPc
+ || mDisplayContent.isPublicSecondaryDisplayWithDesktopModeForceEnabled()
|| !mDisplayContent.shouldRotateWithContent())
// For debug purposes the next line turns this feature off with:
// $ adb shell setprop config.override_forced_orient true
@@ -1659,7 +1660,8 @@ public class DisplayRotation {
void removeDefaultDisplayRotationChangedCallback() {
if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)) {
- mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mDisplayRotationCoordinator.removeDefaultDisplayRotationChangedCallback(
+ mDefaultDisplayRotationChangedCallback);
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
index ae3787cffa23..01e1b1342989 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCoordinator.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.util.Slog;
import android.view.Display;
import android.view.Surface;
@@ -40,6 +41,7 @@ class DisplayRotationCoordinator {
@Nullable
@VisibleForTesting
Runnable mDefaultDisplayRotationChangedCallback;
+ private int mCallbackDisplayId = Display.INVALID_DISPLAY;
@Surface.Rotation
private int mDefaultDisplayCurrentRotation;
@@ -68,12 +70,15 @@ class DisplayRotationCoordinator {
* Register a callback to be notified when the default display's rotation changes. Clients can
* query the default display's current rotation via {@link #getDefaultDisplayCurrentRotation()}.
*/
- void setDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
- if (mDefaultDisplayRotationChangedCallback != null) {
- throw new UnsupportedOperationException("Multiple clients unsupported");
+ void setDefaultDisplayRotationChangedCallback(int displayId, @NonNull Runnable callback) {
+ if (mDefaultDisplayRotationChangedCallback != null && displayId != mCallbackDisplayId) {
+ throw new UnsupportedOperationException("Multiple clients unsupported"
+ + ". Incoming displayId: " + displayId
+ + ", existing displayId: " + mCallbackDisplayId);
}
mDefaultDisplayRotationChangedCallback = callback;
+ mCallbackDisplayId = displayId;
if (mDefaultDisplayCurrentRotation != mDefaultDisplayDefaultRotation) {
callback.run();
@@ -82,10 +87,17 @@ class DisplayRotationCoordinator {
/**
* Removes the callback that was added via
- * {@link #setDefaultDisplayRotationChangedCallback(Runnable)}.
+ * {@link #setDefaultDisplayRotationChangedCallback(int, Runnable)}.
*/
- void removeDefaultDisplayRotationChangedCallback() {
+ void removeDefaultDisplayRotationChangedCallback(@NonNull Runnable callback) {
+ if (callback != mDefaultDisplayRotationChangedCallback) {
+ Slog.w(TAG, "Attempted to remove non-matching callback."
+ + " DisplayId: " + mCallbackDisplayId);
+ return;
+ }
+
mDefaultDisplayRotationChangedCallback = null;
+ mCallbackDisplayId = Display.INVALID_DISPLAY;
}
static boolean isSecondaryInternalDisplay(@NonNull DisplayContent displayContent) {
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index c87b811b4231..f6d05d08cb04 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -143,7 +143,7 @@ class DisplayWindowSettings {
}
// No record is present so use default windowing mode policy.
final boolean forceFreeForm = mService.mAtmService.mSupportsFreeformWindowManagement
- && (mService.mIsPc || dc.forceDesktopMode());
+ && (mService.mIsPc || dc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
if (forceFreeForm) {
return WindowConfiguration.WINDOWING_MODE_FREEFORM;
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index c89feb41e723..46312aff1fb6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2853,11 +2853,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
void prepareForShutdown() {
+ mWindowManager.mSnapshotController.mTaskSnapshotController.prepareShutdown();
for (int i = 0; i < getChildCount(); i++) {
- final int displayId = getChildAt(i).mDisplayId;
- mWindowManager.mSnapshotController.mTaskSnapshotController
- .snapshotForShutdown(displayId);
- createSleepToken("shutdown", displayId);
+ createSleepToken("shutdown", getChildAt(i).mDisplayId);
}
}
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index bd8e8f4008de..8b63ecf7135f 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -103,12 +103,42 @@ class SnapshotPersistQueue {
}
/**
- * Write out everything in the queue because of shutdown.
+ * Prepare to enqueue all visible task snapshots because of shutdown.
*/
- void shutdown() {
+ void prepareShutdown() {
synchronized (mLock) {
mShutdown = true;
- mLock.notifyAll();
+ }
+ }
+
+ private boolean isQueueEmpty() {
+ synchronized (mLock) {
+ return mWriteQueue.isEmpty() || mQueueIdling || mPaused;
+ }
+ }
+
+ void waitFlush(long timeout) {
+ if (timeout <= 0) {
+ return;
+ }
+ final long endTime = System.currentTimeMillis() + timeout;
+ while (true) {
+ if (!isQueueEmpty()) {
+ long timeRemaining = endTime - System.currentTimeMillis();
+ if (timeRemaining > 0) {
+ synchronized (mLock) {
+ try {
+ mLock.wait(timeRemaining);
+ } catch (InterruptedException e) {
+ }
+ }
+ } else {
+ Slog.w(TAG, "Snapshot Persist Queue flush timed out");
+ break;
+ }
+ } else {
+ break;
+ }
}
}
@@ -139,7 +169,9 @@ class SnapshotPersistQueue {
mWriteQueue.addLast(item);
}
item.onQueuedLocked();
- ensureStoreQueueDepthLocked();
+ if (!mShutdown) {
+ ensureStoreQueueDepthLocked();
+ }
if (!mPaused) {
mLock.notifyAll();
}
@@ -213,6 +245,9 @@ class SnapshotPersistQueue {
if (!writeQueueEmpty && !mPaused) {
continue;
}
+ if (mShutdown && writeQueueEmpty) {
+ mLock.notifyAll();
+ }
try {
mQueueIdling = writeQueueEmpty;
mLock.wait();
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 9fe3f7563902..c130931277fe 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -309,23 +309,31 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
/**
* Record task snapshots before shutdown.
*/
- void snapshotForShutdown(int displayId) {
+ void prepareShutdown() {
if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
- final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
- if (displayContent == null) {
+ // Make write items run in a batch.
+ mPersister.mSnapshotPersistQueue.setPaused(true);
+ mPersister.mSnapshotPersistQueue.prepareShutdown();
+ for (int i = 0; i < mService.mRoot.getChildCount(); i++) {
+ mService.mRoot.getChildAt(i).forAllLeafTasks(task -> {
+ if (task.isVisible() && !task.isActivityTypeHome()) {
+ final TaskSnapshot snapshot = captureSnapshot(task);
+ if (snapshot != null) {
+ mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+ }
+ }
+ }, true /* traverseTopToBottom */);
+ }
+ mPersister.mSnapshotPersistQueue.setPaused(false);
+ }
+
+ void waitFlush(long timeout) {
+ if (!com.android.window.flags.Flags.recordTaskSnapshotsBeforeShutdown()) {
return;
}
- displayContent.forAllLeafTasks(task -> {
- if (task.isVisible() && !task.isActivityTypeHome()) {
- final TaskSnapshot snapshot = captureSnapshot(task);
- if (snapshot != null) {
- mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
- }
- }
- }, true /* traverseTopToBottom */);
- mPersister.mSnapshotPersistQueue.shutdown();
+ mPersister.mSnapshotPersistQueue.waitFlush(timeout);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7a53ccf6110c..9e1509cf95cc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7700,7 +7700,7 @@ public class WindowManagerService extends IWindowManager.Stub
+ "not exist: %d", displayId);
return false;
}
- return displayContent.supportsSystemDecorations();
+ return displayContent.isSystemDecorationsSupported();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 82947377d01e..cebe790bb1b9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2757,7 +2757,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* Expands the given rectangle by the region of window resize handle for freeform window.
* @param inOutRect The rectangle to update.
*/
- private void adjustRegionInFreefromWindowMode(Rect inOutRect) {
+ private void adjustRegionInFreeformWindowMode(Rect inOutRect) {
if (!inFreeformWindowingMode()) {
return;
}
@@ -2808,7 +2808,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
}
- adjustRegionInFreefromWindowMode(mTmpRect);
+ adjustRegionInFreeformWindowMode(mTmpRect);
outRegion.set(mTmpRect);
cropRegionToRootTaskBoundsIfNeeded(outRegion);
}
@@ -3608,7 +3608,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
rootTask.getDimBounds(mTmpRect);
- adjustRegionInFreefromWindowMode(mTmpRect);
+ adjustRegionInFreeformWindowMode(mTmpRect);
region.op(mTmpRect, Region.Op.INTERSECT);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index a1937cec706c..9b8a7cca7358 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -23,6 +23,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -33,20 +35,24 @@ import android.content.res.Resources;
import android.location.ILocationListener;
import android.location.LocationManagerInternal;
import android.location.LocationRequest;
+import android.location.flags.Flags;
import android.location.provider.ProviderRequest;
import android.os.IBinder;
import android.os.PowerManager;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
import com.android.server.location.provider.AbstractLocationProvider;
import com.android.server.location.provider.LocationProviderManager;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
import com.google.common.util.concurrent.MoreExecutors;
@@ -54,6 +60,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -75,8 +82,12 @@ public class LocationManagerServiceTest {
private TestInjector mInjector;
private LocationManagerService mLocationManagerService;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Spy private FakeAbstractLocationProvider mProviderWithPermission;
@Spy private FakeAbstractLocationProvider mProviderWithoutPermission;
+ @Mock private ProxyPopulationDensityProvider mPopulationDensityProvider;
@Mock private ILocationListener mLocationListener;
@Mock private IBinder mBinder;
@Mock private Context mContext;
@@ -172,6 +183,32 @@ public class LocationManagerServiceTest {
}
@Test
+ public void testSetLocationFudgerCache_withFeatureFlagDisabled_isNotCalled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager, never()).setLocationFudgerCache(any());
+ }
+
+ @Test
+ public void testSetLocationFudgerCache_withFeatureFlagEnabled_isCalled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationProviderManager manager = mock(LocationProviderManager.class);
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ mLocationManagerService.addLocationProviderManager(manager, /* provider = */ null);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mLocationManagerService.setLocationFudgerCache(cache);
+
+ verify(manager).setLocationFudgerCache(cache);
+ }
+
+ @Test
public void testHasProvider_noPermission() {
assertThat(mLocationManagerService.hasProvider(PROVIDER_WITHOUT_PERMISSION)).isFalse();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
new file mode 100644
index 000000000000..04b82c4890af
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerCacheTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.fudger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.location.flags.Flags;
+import android.location.provider.IS2CellIdsCallback;
+import android.location.provider.IS2LevelCallback;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.location.geometry.S2CellIdUtils;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS)
+public class LocationFudgerCacheTest {
+
+ private static final String TAG = "LocationFudgerCacheTest";
+
+ private static final long TIMES_SQUARE_S2_ID =
+ S2CellIdUtils.fromLatLngDegrees(40.758896, -73.985130);
+
+ private static final double[] POINT_IN_TIMES_SQUARE = {40.75889599346095, -73.9851300385147};
+
+ private static final double[] POINT_OUTSIDE_TIMES_SQUARE = {48.858093, 2.294694};
+
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
+ public void hasDefaultValue_isInitiallyFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_uponQueryError_isStillFalse()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ assertThat(cache.hasDefaultValue()).isFalse();
+ }
+
+ @Test
+ public void hasDefaultValue_afterSuccessfulQuery_isTrue()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ assertThat(cache.hasDefaultValue()).isTrue();
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedOutsideOfCache_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ int defaultLevel = 2;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(defaultLevel);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_OUTSIDE_TIMES_SQUARE[0],
+ POINT_OUTSIDE_TIMES_SQUARE[1])).isEqualTo(defaultLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueriedValueIsCached_returnsCachedValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+ Long s2Cell = S2CellIdUtils.getParent(TIMES_SQUARE_S2_ID, level);
+
+ cache.addToCache(s2Cell);
+
+ assertThat(cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]))
+ .isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenStarting_queriesDefaultValue() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifDidntGetDefaultValue_queriesItAgain() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ verify(provider, times(2)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_ifReceivedDefaultValue_doesNotQueriesIt()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider, times(1)).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(10);
+
+ cache.getCoarseningLevel(90.0, 0.0);
+
+ // Verify getDefaultCoarseningLevel did not get called again
+ verify(provider, times(1)).getDefaultCoarseningLevel(any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenSuccessfullyQueriesDefaultValue_storesResult()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int level = 10;
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onResult(level);
+
+ // Query any uncached location
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(level);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryingDefaultValueFails_returnsDefault()
+ throws RemoteException {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ ArgumentCaptor<IS2LevelCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(argumentCaptor.capture());
+
+ IS2LevelCallback cb = argumentCaptor.getValue();
+ cb.onError();
+
+ // Query any uncached location. The default value is 0
+ assertThat(cache.getCoarseningLevel(0.0, 0.0)).isEqualTo(0);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsNotCached_queriesProvider() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.getCoarseningLevel(POINT_IN_TIMES_SQUARE[0], POINT_IN_TIMES_SQUARE[1]);
+
+ verify(provider).getCoarsenedS2Cell(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), any());
+ }
+
+ @Test
+ public void locationFudgerCache_whenProviderIsQueried_resultIsCached() throws RemoteException {
+ double lat = POINT_IN_TIMES_SQUARE[0];
+ double lng = POINT_IN_TIMES_SQUARE[1];
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ int level = cache.getCoarseningLevel(lat, lng);
+ assertThat(level).isEqualTo(0); // default value
+
+ ArgumentCaptor<IS2CellIdsCallback> argumentCaptor = ArgumentCaptor.forClass(
+ IS2CellIdsCallback.class);
+ verify(provider).getCoarsenedS2Cell(eq(POINT_IN_TIMES_SQUARE[0]),
+ eq(POINT_IN_TIMES_SQUARE[1]), argumentCaptor.capture());
+
+ // Results from the proxy should set the cache
+ int expectedLevel = 4;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(lat, lng);
+ Long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+ IS2CellIdsCallback cb = argumentCaptor.getValue();
+ long[] answer = new long[] {s2CellId};
+ cb.onResult(answer);
+
+ int level2 = cache.getCoarseningLevel(lat, lng);
+ assertThat(level2).isEqualTo(expectedLevel);
+ }
+
+ @Test
+ public void locationFudgerCache_whenQueryIsCached_doesNotRefreshIt() {
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ cache.addToCache(TIMES_SQUARE_S2_ID);
+
+ verify(provider, never()).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
+ @Test
+ public void locationFudgerCache_canContainUpToMaxSizeItems() {
+ // This test has two sequences of arrange-act-assert.
+ // The first checks that the cache correctly store up to MAX_CACHE_SIZE items.
+ // The second checks that any new element replaces the oldest in the cache.
+
+ // Arrange.
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int size = cache.MAX_CACHE_SIZE;
+
+ double[][] latlngs = new double[size][2];
+ long[] cells = new long[size];
+ int[] expectedLevels = new int[size];
+
+ for (int i = 0; i < size; i++) {
+ // Create arbitrary lat/lngs.
+ latlngs[i][0] = 10.0 * i;
+ latlngs[i][1] = 10.0 * i;
+
+ expectedLevels[i] = 10; // we set some arbitrary S2 level for each latlng.
+
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(latlngs[i][0], latlngs[i][1]);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevels[i]);
+ cells[i] = s2CellId;
+ }
+
+ // Act.
+ cache.addToCache(cells);
+
+ // Assert: check that the cache contains these latlngs and returns the correct level.
+ for (int i = 0; i < size; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+
+ // Second assertion: A new value evicts the oldest one.
+
+ // Arrange.
+ int expectedLevel = 25;
+ long leafCell = S2CellIdUtils.fromLatLngDegrees(-10.0, -180.0);
+ long s2CellId = S2CellIdUtils.getParent(leafCell, expectedLevel);
+
+ // Act.
+ cache.addToCache(s2CellId);
+
+ // Assert: the new point is in the cache.
+ assertThat(cache.getCoarseningLevel(-10.0, -180.0)).isEqualTo(expectedLevel);
+ // Assert: all but the oldest point are still in cache.
+ for (int i = 0; i < size - 1; i++) {
+ assertThat(cache.getCoarseningLevel(latlngs[i][0], latlngs[i][1]))
+ .isEqualTo(expectedLevels[i]);
+ }
+ // Assert: the oldest point has been evicted.
+ assertThat(cache.getCoarseningLevel(latlngs[size - 1][0], latlngs[size - 1][1]))
+ .isEqualTo(0);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
index 4e9b6c78edfd..d58e772f991d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/fudger/LocationFudgerTest.java
@@ -22,14 +22,23 @@ import static com.android.server.location.LocationUtils.createLocation;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyDouble;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.location.Location;
+import android.location.flags.Flags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -55,6 +64,9 @@ public class LocationFudgerTest {
private LocationFudger mFudger;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Before
public void setUp() {
long seed = System.currentTimeMillis();
@@ -162,4 +174,64 @@ public class LocationFudgerTest {
input.getLongitude() + deltaYM / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR,
0);
}
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsDisabled_cacheIsNotUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledButNotDefault_cacheIsNotUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(false).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ mFudger.createCoarse(createLocation("test", mRandom));
+
+ verify(cache, never()).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_ifFeatureIsEnabledAndDefaultIsSet_cacheIsUsed() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ LocationFudgerCache cache = mock(LocationFudgerCache.class);
+ doReturn(true).when(cache).hasDefaultValue();
+
+ mFudger.setLocationFudgerCache(cache);
+
+ Location fine = createLocation("test", mRandom);
+ mFudger.createCoarse(fine);
+
+ // We can't verify that the coordinatese of "fine" are passed to the API due to the addition
+ // of the offset. We must use anyDouble().
+ verify(cache).getCoarseningLevel(anyDouble(), anyDouble());
+ }
+
+ @Test
+ public void testDensityBasedCoarsening_newAlgorithm_snapsToCenterOfS2Cell_testVector() {
+ // NB: a complete test vector is in
+ // frameworks/base/services/tests/mockingservicestests/src/com/android/server/...
+ // location/geometry/S2CellIdUtilsTest.java
+
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ // Arbitrary location in Times Square, NYC
+ double[] latLng = new double[] {40.758896, -73.985130};
+ int s2Level = 1;
+ // The level-2 S2 cell around this location is "8c", its center is:
+ double[] expected = { 21.037511025421814, -67.38013505195958 };
+
+ double[] center = mFudger.snapToCenterOfS2Cell(latLng[0], latLng[1], s2Level);
+
+ assertThat(center[0]).isEqualTo(expected[0]);
+ assertThat(center[1]).isEqualTo(expected[1]);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 09282646ff68..cd1990407806 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -40,6 +40,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -72,6 +73,7 @@ import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.flags.Flags;
import android.location.provider.IProviderRequestListener;
+import android.location.provider.IS2LevelCallback;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
@@ -98,8 +100,10 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.location.fudger.LocationFudgerCache;
import com.android.server.location.injector.FakeUserInfoHelper;
import com.android.server.location.injector.TestInjector;
+import com.android.server.location.provider.proxy.ProxyPopulationDensityProvider;
import org.junit.After;
import org.junit.Before;
@@ -1432,6 +1436,72 @@ public class LocationProviderManagerTest {
PERMISSION_FINE)).isEqualTo(location);
}
+ @Test
+ public void testLocationFudger_withFlagDisabled_cacheIsNotSetAndOldAlgoIsUsed() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabledButNoDefaults_oldAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+
+ // Act: the provider didn't provide a default
+ cb.onError();
+
+ Location test = new Location("any-provider");
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ verify(provider, never()).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
+ @Test
+ public void testLocationFudger_withFlagEnabled_cacheIsSetAndNewAlgoIsUsed()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(Flags.FLAG_DENSITY_BASED_COARSE_LOCATIONS);
+ createManager("some-other-name");
+ ProxyPopulationDensityProvider provider = mock(ProxyPopulationDensityProvider.class);
+ LocationFudgerCache cache = new LocationFudgerCache(provider);
+ int defaultLevel = 2;
+
+ mManager.setLocationFudgerCache(cache);
+
+ ArgumentCaptor<IS2LevelCallback> captor = ArgumentCaptor.forClass(IS2LevelCallback.class);
+ verify(provider).getDefaultCoarseningLevel(captor.capture());
+
+ IS2LevelCallback cb = captor.getValue();
+ cb.onResult(defaultLevel);
+
+ Location test = new Location("any-provider");
+ test.setLatitude(10.0);
+ test.setLongitude(20.0);
+ mManager.getPermittedLocation(test, PERMISSION_COARSE);
+
+ // We can't test that 10.0, 20.0 was passed due to the offset. We only test that a call
+ // happened.
+ verify(provider).getCoarsenedS2Cell(anyDouble(), anyDouble(), any());
+ }
+
@MediumTest
@Test
public void testEnableMsl_expectedBehavior() throws Exception {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index e32841909643..194d48a80a65 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -2796,6 +2796,12 @@ public class VibratorManagerServiceTest {
stopAutoDispatcherAndDispatchAll();
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onStarted(any(IVibrationSession.class));
verify(callback, never()).onFinishing();
verify(callback, never()).onFinished(anyInt());
@@ -2817,6 +2823,12 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onFinishing();
verify(callback)
.onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2838,6 +2850,12 @@ public class VibratorManagerServiceTest {
assertThat(session).isNull();
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -2860,6 +2878,12 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onFinishing();
verify(callback, times(2))
.onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
@@ -2886,6 +2910,12 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionStarted(anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionVibrations(anyInt(), anyInt());
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
verify(callback, never()).onStarted(any(IVibrationSession.class));
verify(callback, never()).onFinishing();
verify(callback)
@@ -2923,6 +2953,12 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -2949,6 +2985,12 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock, never())
+ .logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -3038,6 +3080,11 @@ public class VibratorManagerServiceTest {
assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(0));
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionInterrupted(anyInt());
}
@Test
@@ -3340,6 +3387,10 @@ public class VibratorManagerServiceTest {
assertThat(service.isVibrating(2)).isFalse();
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(1));
}
@Test
@@ -3391,6 +3442,10 @@ public class VibratorManagerServiceTest {
assertThat(service.isVibrating(2)).isFalse();
verify(callback).onFinishing();
verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID));
+ verify(mVibratorFrameworkStatsLoggerMock)
+ .logVibrationVendorSessionVibrations(eq(UID), eq(2));
}
@Test
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
index 6dba96766b48..a9a1f93e5ab8 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger/ConversionUtilTest.java
@@ -76,7 +76,7 @@ public class ConversionUtilTest {
var apiconfig = new SoundTrigger.RecognitionConfig.Builder()
.setCaptureRequested(true)
- .setAllowMultipleTriggers(false) // must be false
+ .setMultipleTriggersAllowed(false) // must be false
.setKeyphrases(keyphrases)
.setData(data)
.setAudioCapabilities(flags)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9408f907d058..9cbea2e2f0ad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -601,12 +601,12 @@ public class DisplayContentTests extends WindowTestsBase {
TYPE_WALLPAPER, TYPE_APPLICATION);
// Verify not waiting for display without system decorations.
- doReturn(false).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(false).when(secondaryDisplay).isSystemDecorationsSupported();
assertFalse(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify waiting for non-drawn windows on display with system decorations.
reset(secondaryDisplay);
- doReturn(true).when(secondaryDisplay).supportsSystemDecorations();
+ doReturn(true).when(secondaryDisplay).isSystemDecorationsSupported();
assertTrue(secondaryDisplay.shouldWaitForSystemDecorWindowsOnBoot());
// Verify not waiting for drawn windows on display with system decorations.
@@ -1865,7 +1865,6 @@ public class DisplayContentTests extends WindowTestsBase {
mRootWindowContainer.getDisplayRotationCoordinator();
final DisplayContent defaultDisplayContent = mDisplayContent;
final DisplayRotation defaultDisplayRotation = defaultDisplayContent.getDisplayRotation();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -1922,7 +1921,6 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayRotationCoordinator coordinator =
mRootWindowContainer.getDisplayRotationCoordinator();
- coordinator.removeDefaultDisplayRotationChangedCallback();
DeviceStateController deviceStateController = mock(DeviceStateController.class);
when(deviceStateController.shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay())
@@ -2263,25 +2261,25 @@ public class DisplayContentTests extends WindowTestsBase {
}
@Test
- public void testForceDesktopMode() {
+ public void testIsPublicSecondaryDisplayWithDesktopModeForceEnabled() {
mWm.mForceDesktopModeOnExternalDisplays = true;
// Not applicable for default display
- assertFalse(mDefaultDisplay.forceDesktopMode());
+ assertFalse(mDefaultDisplay.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Not applicable for private secondary display.
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.copyFrom(mDisplayInfo);
displayInfo.flags = FLAG_PRIVATE;
final DisplayContent privateDc = createNewDisplay(displayInfo);
- assertFalse(privateDc.forceDesktopMode());
+ assertFalse(privateDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Applicable for public secondary display.
final DisplayContent publicDc = createNewDisplay();
- assertTrue(publicDc.forceDesktopMode());
+ assertTrue(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
// Make sure forceDesktopMode() is false when the force config is disabled.
mWm.mForceDesktopModeOnExternalDisplays = false;
- assertFalse(publicDc.forceDesktopMode());
+ assertFalse(publicDc.isPublicSecondaryDisplayWithDesktopModeForceEnabled());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
index 4557df0e9c98..266ffffabf15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCoordinatorTests.java
@@ -40,6 +40,9 @@ import org.junit.Test;
@Presubmit
public class DisplayRotationCoordinatorTests {
+ private static final int FIRST_DISPLAY_ID = 1;
+ private static final int SECOND_DISPLAY_ID = 2;
+
@NonNull
private final DisplayRotationCoordinator mCoordinator = new DisplayRotationCoordinator();
@@ -50,22 +53,45 @@ public class DisplayRotationCoordinatorTests {
}
@Test (expected = UnsupportedOperationException.class)
- public void testSecondRegistrationWithoutRemovingFirst() {
+ public void testSecondRegistrationWithoutRemovingFirstWhenDifferentDisplay() {
Runnable callback1 = mock(Runnable.class);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
}
@Test
+ public void testSecondRegistrationWithoutRemovingFirstWhenSameDisplay() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
+ public void testRemoveIncorrectRegistration() {
+ Runnable callback1 = mock(Runnable.class);
+ Runnable callback2 = mock(Runnable.class);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback2);
+ assertEquals(callback1, mCoordinator.mDefaultDisplayRotationChangedCallback);
+
+ // FIRST_DISPLAY_ID is still able to register another callback because the previous
+ // removal should not have succeeded.
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback2);
+ assertEquals(callback2, mCoordinator.mDefaultDisplayRotationChangedCallback);
+ }
+
+ @Test
public void testSecondRegistrationAfterRemovingFirst() {
Runnable callback1 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback1);
- mCoordinator.removeDefaultDisplayRotationChangedCallback();
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback1);
+ mCoordinator.removeDefaultDisplayRotationChangedCallback(callback1);
Runnable callback2 = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback2);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(SECOND_DISPLAY_ID, callback2);
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
verify(callback2).run();
@@ -75,7 +101,7 @@ public class DisplayRotationCoordinatorTests {
@Test
public void testRegisterThenDefaultDisplayRotationChanged() {
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
assertEquals(Surface.ROTATION_0, mCoordinator.getDefaultDisplayCurrentRotation());
verify(callback, never()).run();
@@ -88,7 +114,7 @@ public class DisplayRotationCoordinatorTests {
public void testDefaultDisplayRotationChangedThenRegister() {
mCoordinator.onDefaultDisplayRotationChanged(Surface.ROTATION_90);
Runnable callback = mock(Runnable.class);
- mCoordinator.setDefaultDisplayRotationChangedCallback(callback);
+ mCoordinator.setDefaultDisplayRotationChangedCallback(FIRST_DISPLAY_ID, callback);
verify(callback).run();
assertEquals(Surface.ROTATION_90, mCoordinator.getDefaultDisplayCurrentRotation());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index b5953839ca8f..f795d93b2487 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -605,7 +605,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
@Test
public void testGetOrCreateRootHomeTask_supportedSecondaryDisplay() {
DisplayContent display = createNewDisplay();
- doReturn(true).when(display).supportsSystemDecorations();
+ doReturn(true).when(display).isSystemDecorationsSupported();
// Remove the current home root task if it exists so a new one can be created below.
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
@@ -622,7 +622,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
public void testGetOrCreateRootHomeTask_unsupportedSystemDecorations() {
DisplayContent display = createNewDisplay();
TaskDisplayArea taskDisplayArea = display.getDefaultTaskDisplayArea();
- doReturn(false).when(display).supportsSystemDecorations();
+ doReturn(false).when(display).isSystemDecorationsSupported();
assertNull(taskDisplayArea.getRootHomeTask());
assertNull(taskDisplayArea.getOrCreateRootHomeTask());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 546b1ba66b08..ccce57a81e41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -204,13 +204,13 @@ class TestDisplayContent extends DisplayContent {
final DisplayPolicy displayPolicy = newDisplay.getDisplayPolicy();
spyOn(displayPolicy);
if (mSystemDecorations) {
- doReturn(true).when(newDisplay).supportsSystemDecorations();
+ doReturn(true).when(newDisplay).isSystemDecorationsSupported();
doReturn(true).when(displayPolicy).hasNavigationBar();
doReturn(true).when(displayPolicy).hasBottomNavigationBar();
} else {
doReturn(false).when(displayPolicy).hasNavigationBar();
doReturn(false).when(displayPolicy).hasStatusBar();
- doReturn(false).when(newDisplay).supportsSystemDecorations();
+ doReturn(false).when(newDisplay).isSystemDecorationsSupported();
}
// Update the display policy to make the screen fully turned on so animation is allowed
displayPolicy.screenTurningOn(null /* screenOnListener */);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index fb031bd01673..01ff67400eb1 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -842,7 +842,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return;
}
- model.setRequested(config.isAllowMultipleTriggers());
+ model.setRequested(config.isMultipleTriggersAllowed());
// TODO: Remove this block if the lower layer supports multiple triggers.
if (model.isRequested()) {
updateRecognitionLocked(model, true);
@@ -964,7 +964,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
RecognitionConfig config = modelData.getRecognitionConfig();
if (config != null) {
// Whether we should continue by starting this again.
- modelData.setRequested(config.isAllowMultipleTriggers());
+ modelData.setRequested(config.isMultipleTriggersAllowed());
}
// TODO: Remove this block if the lower layer supports multiple triggers.
if (modelData.isRequested()) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 19a6ddc3a931..e0cdbddd2efb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1445,7 +1445,7 @@ public class SoundTriggerService extends SystemService {
runOrAddOperation(new Operation(
// always execute:
() -> {
- if (!mRecognitionConfig.isAllowMultipleTriggers()) {
+ if (!mRecognitionConfig.isMultipleTriggersAllowed()) {
// Unregister this remoteService once op is done
synchronized (mCallbacksLock) {
mCallbacks.remove(mPuuid.getUuid());
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ad5d42ad5a68..024d7f580a53 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5251,6 +5251,36 @@ public class TelephonyManager {
}
/**
+ * Returns the Group Identifier Level 2 in hexadecimal format.
+ * @return the Group Identifier Level 2 for the SIM card.
+ * Return null if it is unavailable.
+ *
+ * @throws UnsupportedOperationException If the device does not have
+ * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_GET_GROUP_ID_LEVEL2)
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
+ @SystemApi
+ @Nullable
+ public String getGroupIdLevel2() {
+ try {
+ IPhoneSubInfo info = getSubscriberInfoService();
+ if (info == null) {
+ return null;
+ }
+ return info.getGroupIdLevel2ForSubscriber(getSubId(), mContext.getOpPackageName(),
+ mContext.getAttributionTag());
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ // This could happen before phone restarts due to crashing
+ return null;
+ }
+ }
+
+ /**
* Returns the phone number string for line 1, for example, the MSISDN
* for a GSM phone for a particular subscription. Return null if it is unavailable.
* <p>
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index 974cc14ae444..71327dcfc3b1 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -83,6 +83,12 @@ interface IPhoneSubInfo {
String getGroupIdLevel1ForSubscriber(int subId, String callingPackage,
String callingFeatureId);
+ /**
+ * Retrieves the Group Identifier Level1 for GSM phones of a subId.
+ */
+ String getGroupIdLevel2ForSubscriber(int subId, String callingPackage,
+ String callingFeatureId);
+
/** @deprecared Use {@link getIccSerialNumberWithFeature(String, String)} instead */
@UnsupportedAppUsage
String getIccSerialNumber(String callingPackage);
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
new file mode 100644
index 000000000000..6573c2c83f20
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/BottomHalfPipAppHelper.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.content.Intent
+import android.tools.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.testapp.ActivityOptions
+
+class BottomHalfPipAppHelper(
+ instrumentation: Instrumentation,
+ private val useLaunchingActivity: Boolean = false,
+) : PipAppHelper(
+ instrumentation,
+ appName = ActivityOptions.BottomHalfPip.LABEL,
+ componentNameMatcher = ActivityOptions.BottomHalfPip.COMPONENT
+ .toFlickerComponent()
+) {
+ override val openAppIntent: Intent
+ get() = super.openAppIntent.apply {
+ component = if (useLaunchingActivity) {
+ ActivityOptions.BottomHalfPip.LAUNCHING_APP_COMPONENT
+ } else {
+ ActivityOptions.BottomHalfPip.COMPONENT
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 9ce8e807f612..7c24a4adca3d 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -347,6 +347,27 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".BottomHalfPipLaunchingActivity"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="BottomHalfPipLaunchingActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ <activity
+ android:name=".BottomHalfPipActivity"
+ android:resizeableActivity="true"
+ android:supportsPictureInPicture="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.BottomHalfPipLaunchingActivity"
+ android:theme="@style/TranslucentTheme"
+ android:label="BottomHalfPipActivity"
+ android:exported="true">
+ </activity>
<activity android:name=".SplitScreenActivity"
android:resizeableActivity="true"
android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index 47d113717ae0..837d050b73ff 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -62,6 +62,12 @@
<item name="android:backgroundDimEnabled">false</item>
</style>
+ <style name="TranslucentTheme" parent="@style/OptOutEdgeToEdge">
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+
<style name="no_starting_window" parent="@style/OptOutEdgeToEdge">
<item name="android:windowDisablePreview">true</item>
</style>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 73625da9dfa5..0c1ac9951d32 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -241,6 +241,21 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".PipActivity");
}
+ public static class BottomHalfPip {
+ public static final String LAUNCHING_APP_LABEL = "BottomHalfPipLaunchingActivity";
+ // Test App > Bottom Half PIP Activity
+ public static final String LABEL = "BottomHalfPipActivity";
+
+ // Use the bottom half layout for PIP Activity
+ public static final String EXTRA_BOTTOM_HALF_LAYOUT = "bottom_half";
+
+ public static final ComponentName LAUNCHING_APP_COMPONENT = new ComponentName(
+ FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".BottomHalfPipLaunchingActivity");
+
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".BottomHalfPipActivity");
+ }
+
public static class SplitScreen {
public static class Primary {
public static final String LABEL = "SplitScreenPrimaryActivity";
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
new file mode 100644
index 000000000000..3d4865572486
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipActivity.java
@@ -0,0 +1,71 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+public class BottomHalfPipActivity extends PipActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setTheme(R.style.TranslucentTheme);
+ updateLayout();
+ }
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ updateLayout();
+ }
+
+ /**
+ * Sets to match parent layout if the activity is
+ * {@link Activity#isInPictureInPictureMode()}. Otherwise, set to bottom half
+ * layout.
+ *
+ * @see #setToBottomHalfMode(boolean)
+ */
+ private void updateLayout() {
+ setToBottomHalfMode(!isInPictureInPictureMode());
+ }
+
+ /**
+ * Sets `useBottomHalfLayout` to `true` to use the bottom half layout. Use the
+ * [LayoutParams.MATCH_PARENT] layout.
+ */
+ private void setToBottomHalfMode(boolean useBottomHalfLayout) {
+ final WindowManager.LayoutParams attrs = getWindow().getAttributes();
+ if (useBottomHalfLayout) {
+ final int taskHeight = getWindowManager().getCurrentWindowMetrics().getBounds()
+ .height();
+ attrs.y = taskHeight / 2;
+ attrs.height = taskHeight / 2;
+ } else {
+ attrs.y = 0;
+ attrs.height = LayoutParams.MATCH_PARENT;
+ }
+ getWindow().setAttributes(attrs);
+ }
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
new file mode 100644
index 000000000000..d9d4361411bb
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BottomHalfPipLaunchingActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.wm.flicker.testapp;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+public class BottomHalfPipLaunchingActivity extends SimpleActivity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Intent intent = new Intent(this, BottomHalfPipActivity.class);
+ startActivity(intent);
+ }
+}