summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/inputmethod/AndroidManifest.xml3
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java7
-rw-r--r--apex/jobscheduler/service/aconfig/device_idle.aconfig10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java3
-rw-r--r--api/StubLibraries.bp118
-rw-r--r--core/java/android/app/ActivityThread.java6
-rw-r--r--core/java/android/app/ClientTransactionHandler.java6
-rw-r--r--core/java/android/app/Notification.java67
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java7
-rw-r--r--core/java/android/app/servertransaction/ActivityRelaunchItem.java7
-rw-r--r--core/java/android/app/servertransaction/ClientTransactionItem.java11
-rw-r--r--core/java/android/app/servertransaction/ConfigurationChangeItem.java8
-rw-r--r--core/java/android/app/servertransaction/LaunchActivityItem.java9
-rw-r--r--core/java/android/app/servertransaction/MoveToDisplayItem.java7
-rw-r--r--core/java/android/app/servertransaction/WindowContextInfoChangeItem.java7
-rw-r--r--core/java/android/app/servertransaction/WindowStateResizeItem.java25
-rw-r--r--core/java/android/os/Parcel.java22
-rw-r--r--core/java/android/permission/flags.aconfig11
-rw-r--r--core/java/android/util/PackageUtils.java5
-rw-r--r--core/java/android/view/ImeBackAnimationController.java5
-rw-r--r--core/java/android/view/animation/BackGestureInterpolator.java26
-rw-r--r--core/java/android/window/RemoteTransitionStub.java37
-rw-r--r--core/java/android/window/TransitionInfo.java5
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java3
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl7
-rw-r--r--core/res/res/values-mcc404/config.xml2
-rw-r--r--core/res/res/values-mcc405/config.xml2
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java78
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java9
-rw-r--r--libs/hostgraphics/ANativeWindow.cpp106
-rw-r--r--libs/hostgraphics/Android.bp12
-rw-r--r--libs/hwui/Android.bp11
-rw-r--r--libs/hwui/RenderNode.cpp22
-rw-r--r--libs/hwui/RootRenderNode.cpp24
-rw-r--r--libs/hwui/RootRenderNode.h2
-rw-r--r--libs/hwui/WebViewFunctorManager.cpp12
-rw-r--r--libs/hwui/WebViewFunctorManager.h22
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp2
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp19
-rw-r--r--libs/hwui/jni/Bitmap.cpp18
-rw-r--r--libs/hwui/jni/android_graphics_DisplayListCanvas.cpp15
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp14
-rw-r--r--libs/hwui/pipeline/skia/SkiaDisplayList.cpp19
-rw-r--r--libs/hwui/platform/host/WebViewFunctorManager.cpp9
-rw-r--r--libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp61
-rw-r--r--libs/hwui/platform/host/renderthread/ReliableSurface.cpp39
-rw-r--r--libs/hwui/platform/host/utils/MessageHandler.h34
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp15
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp4
-rw-r--r--libs/hwui/renderthread/ReliableSurface.h4
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp18
-rw-r--r--media/java/android/media/session/ISessionManager.aidl3
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml2
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip (renamed from packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip)bin132788867 -> 134184980 bytes
-rw-r--r--packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/res/values/strings.xml15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java50
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java33
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java29
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java17
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java19
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java196
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java19
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java49
-rw-r--r--packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java20
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt1
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt126
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt33
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt2
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt246
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt473
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt5
-rw-r--r--services/Android.bp24
-rw-r--r--services/core/java/com/android/server/SystemServiceManager.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java27
-rw-r--r--services/core/java/com/android/server/am/ProcessErrorStateRecord.java23
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java6
-rw-r--r--services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java464
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java111
-rw-r--r--services/core/java/com/android/server/inputmethod/ZeroJankProxy.java157
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java19
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java5
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java99
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java174
-rw-r--r--services/core/java/com/android/server/media/MediaShellCommand.java6
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java42
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java14
-rw-r--r--services/core/java/com/android/server/power/PowerManagerShellCommand.java18
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java5
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/Session.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java13
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java20
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPolicy.kt9
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt2
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt16
-rw-r--r--services/proguard.flags5
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java308
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java1
-rw-r--r--tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt3
208 files changed, 3972 insertions, 1628 deletions
diff --git a/apct-tests/perftests/inputmethod/AndroidManifest.xml b/apct-tests/perftests/inputmethod/AndroidManifest.xml
index 3eea418fe5c7..5dd6ccccfb1c 100644
--- a/apct-tests/perftests/inputmethod/AndroidManifest.xml
+++ b/apct-tests/perftests/inputmethod/AndroidManifest.xml
@@ -22,7 +22,8 @@
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity"
- android:exported="true">
+ android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
+ android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
index f3bea17b2f0d..0c2ee8cb238a 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
@@ -19,7 +19,9 @@ package android.perftests.utils;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.graphics.Insets;
import android.os.Bundle;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -42,6 +44,11 @@ public class PerfTestActivity extends Activity {
if (getIntent().getBooleanExtra(INTENT_EXTRA_ADD_EDIT_TEXT, false)) {
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setOnApplyWindowInsetsListener((v, w) -> {
+ final Insets insets = w.getSystemWindowInsets();
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsets.CONSUMED;
+ });
final EditText editText = new EditText(this);
editText.setId(ID_EDITOR);
diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig
index e8c99b12828f..c4d0d1850a18 100644
--- a/apex/jobscheduler/service/aconfig/device_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig
@@ -2,6 +2,16 @@ package: "com.android.server.deviceidle"
container: "system"
flag {
+ name: "remove_idle_location"
+ namespace: "location"
+ description: "Remove DeviceIdleController usage of location"
+ bug: "332770178"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "disable_wakelocks_in_light_idle"
namespace: "backstage_power"
description: "Disable wakelocks for background apps while Light Device Idle is active"
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 4832ea624bd7..11fa7b75182f 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -109,6 +109,7 @@ import com.android.modules.expresslog.Counter;
import com.android.server.am.BatteryStatsService;
import com.android.server.deviceidle.ConstraintController;
import com.android.server.deviceidle.DeviceIdleConstraintTracker;
+import com.android.server.deviceidle.Flags;
import com.android.server.deviceidle.IDeviceIdleConstraint;
import com.android.server.deviceidle.TvConstraintController;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -2558,7 +2559,7 @@ public class DeviceIdleController extends SystemService
}
boolean isLocationPrefetchEnabled() {
- return mContext.getResources().getBoolean(
+ return !Flags.removeIdleLocation() && mContext.getResources().getBoolean(
com.android.internal.R.bool.config_autoPowerModePrefetchLocation);
}
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 1b1bc6b9afdb..f56a95011dee 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -54,34 +54,34 @@ non_updatable_exportable_droidstubs {
baseline_file: ":non-updatable-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
@@ -134,34 +134,34 @@ non_updatable_exportable_droidstubs {
baseline_file: ":non-updatable-system-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
@@ -189,56 +189,58 @@ non_updatable_exportable_droidstubs {
baseline_file: ":non-updatable-test-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "removed.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "removed.txt",
tag: ".exportable.removed-api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "removed.txt",
tag: ".removed-api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
@@ -271,34 +273,34 @@ non_updatable_exportable_droidstubs {
baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f64418587918..eaa23b9db166 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3727,12 +3727,6 @@ public final class ActivityThread extends ClientTransactionHandler
return mActivities.get(token);
}
- @Nullable
- @Override
- public Context getWindowContext(@NonNull IBinder clientToken) {
- return WindowTokenClientController.getInstance().getWindowContext(clientToken);
- }
-
@VisibleForTesting(visibility = PACKAGE)
public Configuration getConfiguration() {
return mConfigurationController.getConfiguration();
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 01153c9e7efc..f0c319673ade 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -23,7 +23,6 @@ import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.TransactionExecutor;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
@@ -32,7 +31,6 @@ import android.util.MergedConfiguration;
import android.view.SurfaceControl;
import android.window.ActivityWindowInfo;
import android.window.SplashScreenView.SplashScreenViewParcelable;
-import android.window.WindowContext;
import android.window.WindowContextInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -90,10 +88,6 @@ public abstract class ClientTransactionHandler {
/** Get activity instance for the token. */
public abstract Activity getActivity(IBinder token);
- /** Gets the {@link WindowContext} instance for the token. */
- @Nullable
- public abstract Context getWindowContext(@NonNull IBinder clientToken);
-
// Prepare phase related logic and handlers. Methods that inform about about pending changes or
// do other internal bookkeeping.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d9e0413e5ad5..dbde7d20f0d8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2655,8 +2655,16 @@ public class Notification implements Parcelable
if (mAllowlistToken == null) {
mAllowlistToken = processAllowlistToken;
}
- // Propagate this token to all pending intents that are unmarshalled from the parcel.
- parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
+ if (Flags.secureAllowlistToken()) {
+ // Propagate this token to all pending intents that are unmarshalled from the parcel,
+ // or keep the one we're already propagating, if that's the case.
+ if (!parcel.hasClassCookie(PendingIntent.class)) {
+ parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
+ }
+ } else {
+ // Propagate this token to all pending intents that are unmarshalled from the parcel.
+ parcel.setClassCookie(PendingIntent.class, mAllowlistToken);
+ }
when = parcel.readLong();
creationTime = parcel.readLong();
@@ -3210,9 +3218,29 @@ public class Notification implements Parcelable
PendingIntent.addOnMarshaledListener(addedListener);
}
try {
- // IMPORTANT: Add marshaling code in writeToParcelImpl as we
- // want to intercept all pending events written to the parcel.
- writeToParcelImpl(parcel, flags);
+ if (Flags.secureAllowlistToken()) {
+ boolean mustClearCookie = false;
+ if (!parcel.hasClassCookie(Notification.class)) {
+ // This is the "root" notification, and not an "inner" notification (including
+ // publicVersion or anything else that might be embedded in extras).
+ parcel.setClassCookie(Notification.class, this);
+ mustClearCookie = true;
+ }
+ try {
+ // IMPORTANT: Add marshaling code in writeToParcelImpl as we
+ // want to intercept all pending events written to the parcel.
+ writeToParcelImpl(parcel, flags);
+ } finally {
+ if (mustClearCookie) {
+ parcel.removeClassCookie(Notification.class, this);
+ }
+ }
+ } else {
+ // IMPORTANT: Add marshaling code in writeToParcelImpl as we
+ // want to intercept all pending events written to the parcel.
+ writeToParcelImpl(parcel, flags);
+ }
+
synchronized (this) {
// Must be written last!
parcel.writeArraySet(allPendingIntents);
@@ -3227,7 +3255,19 @@ public class Notification implements Parcelable
private void writeToParcelImpl(Parcel parcel, int flags) {
parcel.writeInt(1);
- parcel.writeStrongBinder(mAllowlistToken);
+ if (Flags.secureAllowlistToken()) {
+ Notification rootNotification = (Notification) parcel.getClassCookie(
+ Notification.class);
+ if (rootNotification != null && rootNotification != this) {
+ // Always use the same token as the root notification
+ parcel.writeStrongBinder(rootNotification.mAllowlistToken);
+ } else {
+ parcel.writeStrongBinder(mAllowlistToken);
+ }
+ } else {
+ parcel.writeStrongBinder(mAllowlistToken);
+ }
+
parcel.writeLong(when);
parcel.writeLong(creationTime);
if (mSmallIcon == null && icon != 0) {
@@ -3620,18 +3660,23 @@ public class Notification implements Parcelable
* Sets the token used for background operations for the pending intents associated with this
* notification.
*
- * This token is automatically set during deserialization for you, you usually won't need to
- * call this unless you want to change the existing token, if any.
+ * Note: Should <em>only</em> be invoked by NotificationManagerService, since this is normally
+ * populated by unparceling (and also used there). Any other usage is suspect.
*
* @hide
*/
- public void clearAllowlistToken() {
- mAllowlistToken = null;
+ public void overrideAllowlistToken(IBinder token) {
+ mAllowlistToken = token;
if (publicVersion != null) {
- publicVersion.clearAllowlistToken();
+ publicVersion.overrideAllowlistToken(token);
}
}
+ /** @hide */
+ public IBinder getAllowlistToken() {
+ return mAllowlistToken;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 1f6ac2efd64f..250953e61137 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -74,6 +74,16 @@ flag {
}
flag {
+ name: "secure_allowlist_token"
+ namespace: "systemui"
+ description: "Prevents allowlist_token from leaking out and foreign tokens from being accepted"
+ bug: "328254922"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "update_ranking_time"
namespace: "systemui"
description: "Updates notification sorting criteria to highlight new content while maintaining stability"
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index 631772556879..11d7ff86ce3d 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -23,7 +23,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -60,12 +59,6 @@ public class ActivityConfigurationChangeItem extends ActivityTransactionItem {
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getActivity(getActivityToken());
- }
-
// ObjectPoolItem implementation
private ActivityConfigurationChangeItem() {}
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index 6da871a74383..45bf235de2cd 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -23,7 +23,6 @@ import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.ResultInfo;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.os.IBinder;
import android.os.Parcel;
@@ -88,12 +87,6 @@ public class ActivityRelaunchItem extends ActivityTransactionItem {
client.reportRelaunch(r);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getActivity(getActivityToken());
- }
-
// ObjectPoolItem implementation
private ActivityRelaunchItem() {}
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index 6e7e93009993..99ebe1b975a4 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -24,7 +24,6 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.os.IBinder;
import android.os.Parcelable;
@@ -53,16 +52,6 @@ public abstract class ClientTransactionItem implements BaseClientRequest, Parcel
return true;
}
- // TODO(b/260873529): cleanup
- /**
- * If this {@link ClientTransactionItem} is updating configuration, returns the {@link Context}
- * it is updating; otherwise, returns {@code null}.
- */
- @Nullable
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return null;
- }
-
/**
* Returns the activity token if this transaction item is activity-targeting. Otherwise,
* returns {@code null}.
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 0e327a7627d1..22da706cc7f4 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -18,9 +18,7 @@ package android.app.servertransaction;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.Parcel;
@@ -48,12 +46,6 @@ public class ConfigurationChangeItem extends ClientTransactionItem {
client.handleConfigurationChanged(mConfiguration, mDeviceId);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return ActivityThread.currentApplication();
- }
-
// ObjectPoolItem implementation
private ConfigurationChangeItem() {}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index f02cb212276b..7dcbebaeba0b 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -24,14 +24,12 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityClient;
import android.app.ActivityOptions.SceneTransitionInfo;
-import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.app.IActivityClientController;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
@@ -121,13 +119,6 @@ public class LaunchActivityItem extends ClientTransactionItem {
client.countLaunchingActivities(-1);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- // LaunchActivityItem may update the global config with #mCurConfig.
- return ActivityThread.currentApplication();
- }
-
// ObjectPoolItem implementation
private LaunchActivityItem() {}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index 0702c4594075..8706edd26406 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -22,7 +22,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
@@ -59,12 +58,6 @@ public class MoveToDisplayItem extends ActivityTransactionItem {
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getActivity(getActivityToken());
- }
-
// ObjectPoolItem implementation
private MoveToDisplayItem() {}
diff --git a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
index cbad92ff3f38..f6a72915e639 100644
--- a/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowContextInfoChangeItem.java
@@ -21,7 +21,6 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ClientTransactionHandler;
-import android.content.Context;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Parcel;
@@ -46,12 +45,6 @@ public class WindowContextInfoChangeItem extends ClientTransactionItem {
client.handleWindowContextInfoChanged(mClientToken, mInfo);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- return client.getWindowContext(mClientToken);
- }
-
// ObjectPoolItem implementation
private WindowContextInfoChangeItem() {}
diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java
index 1817c5eefb14..da99096f022a 100644
--- a/core/java/android/app/servertransaction/WindowStateResizeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java
@@ -22,10 +22,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.Trace;
@@ -59,10 +56,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
/** {@code null} if this is not an Activity window. */
@Nullable
- private IBinder mActivityToken;
-
- /** {@code null} if this is not an Activity window. */
- @Nullable
private ActivityWindowInfo mActivityWindowInfo;
@Override
@@ -86,14 +79,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
- @Nullable
- @Override
- public Context getContextToUpdate(@NonNull ClientTransactionHandler client) {
- // TODO(b/260873529): dispatch for mActivityToken as well.
- // WindowStateResizeItem may update the global config with #mConfiguration.
- return ActivityThread.currentApplication();
- }
-
// ObjectPoolItem implementation
private WindowStateResizeItem() {}
@@ -103,8 +88,7 @@ public class WindowStateResizeItem extends ClientTransactionItem {
@NonNull ClientWindowFrames frames, boolean reportDraw,
@NonNull MergedConfiguration configuration, @NonNull InsetsState insetsState,
boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
- boolean dragResizing, @Nullable IBinder activityToken,
- @Nullable ActivityWindowInfo activityWindowInfo) {
+ boolean dragResizing, @Nullable ActivityWindowInfo activityWindowInfo) {
WindowStateResizeItem instance =
ObjectPool.obtain(WindowStateResizeItem.class);
if (instance == null) {
@@ -120,7 +104,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
instance.mDisplayId = displayId;
instance.mSyncSeqId = syncSeqId;
instance.mDragResizing = dragResizing;
- instance.mActivityToken = activityToken;
instance.mActivityWindowInfo = activityWindowInfo != null
? new ActivityWindowInfo(activityWindowInfo)
: null;
@@ -140,7 +123,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
mDisplayId = INVALID_DISPLAY;
mSyncSeqId = -1;
mDragResizing = false;
- mActivityToken = null;
mActivityWindowInfo = null;
ObjectPool.recycle(this);
}
@@ -160,7 +142,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
dest.writeInt(mDisplayId);
dest.writeInt(mSyncSeqId);
dest.writeBoolean(mDragResizing);
- dest.writeStrongBinder(mActivityToken);
dest.writeTypedObject(mActivityWindowInfo, flags);
}
@@ -176,7 +157,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
mDisplayId = in.readInt();
mSyncSeqId = in.readInt();
mDragResizing = in.readBoolean();
- mActivityToken = in.readStrongBinder();
mActivityWindowInfo = in.readTypedObject(ActivityWindowInfo.CREATOR);
}
@@ -209,7 +189,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
&& mDisplayId == other.mDisplayId
&& mSyncSeqId == other.mSyncSeqId
&& mDragResizing == other.mDragResizing
- && Objects.equals(mActivityToken, other.mActivityToken)
&& Objects.equals(mActivityWindowInfo, other.mActivityWindowInfo);
}
@@ -226,7 +205,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
result = 31 * result + mDisplayId;
result = 31 * result + mSyncSeqId;
result = 31 * result + (mDragResizing ? 1 : 0);
- result = 31 * result + Objects.hashCode(mActivityToken);
result = 31 * result + Objects.hashCode(mActivityWindowInfo);
return result;
}
@@ -236,7 +214,6 @@ public class WindowStateResizeItem extends ClientTransactionItem {
return "WindowStateResizeItem{window=" + mWindow
+ ", reportDrawn=" + mReportDraw
+ ", configuration=" + mConfiguration
- + ", activityToken=" + mActivityToken
+ ", activityWindowInfo=" + mActivityWindowInfo
+ "}";
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 35a3a5f32648..136c45d1695f 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -863,6 +863,28 @@ public final class Parcel {
}
/** @hide */
+ public void removeClassCookie(Class clz, Object expectedCookie) {
+ if (mClassCookies != null) {
+ Object removedCookie = mClassCookies.remove(clz);
+ if (removedCookie != expectedCookie) {
+ Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz
+ + ") but instead removed " + removedCookie);
+ }
+ } else {
+ Log.wtf(TAG, "Expected to remove " + expectedCookie + " (with key=" + clz
+ + ") but no cookies were present");
+ }
+ }
+
+ /**
+ * Whether {@link #setClassCookie} has been called with the specified {@code clz}.
+ * @hide
+ */
+ public boolean hasClassCookie(Class clz) {
+ return mClassCookies != null && mClassCookies.containsKey(clz);
+ }
+
+ /** @hide */
public final void adoptClassCookies(Parcel from) {
mClassCookies = from.mClassCookies;
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index ec3c9789f655..23ece310b926 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -155,14 +155,3 @@ flag {
description: "Use runtime permission state to determine appop state"
bug: "266164193"
}
-
-flag {
- name: "ignore_apex_permissions"
- is_fixed_read_only: true
- namespace: "permissions"
- description: "Ignore APEX pacakges for permissions on V+"
- bug: "301320911"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index ea7efc79de87..c1ed19fef032 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -203,9 +203,8 @@ public final class PackageUtils {
}
File f = new File(filePath);
- try {
- DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f),
- messageDigest);
+ try (DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f),
+ messageDigest)) {
while (digestInputStream.read(fileBuffer) != -1);
} catch (IOException e) {
e.printStackTrace();
diff --git a/core/java/android/view/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java
index d14e858d9fa1..665fac18be99 100644
--- a/core/java/android/view/ImeBackAnimationController.java
+++ b/core/java/android/view/ImeBackAnimationController.java
@@ -28,6 +28,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
import android.util.Log;
+import android.view.animation.BackGestureInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.window.BackEvent;
@@ -44,7 +45,7 @@ public class ImeBackAnimationController implements OnBackAnimationCallback {
private static final int POST_COMMIT_DURATION_MS = 200;
private static final int POST_COMMIT_CANCEL_DURATION_MS = 50;
private static final float PEEK_FRACTION = 0.1f;
- private static final Interpolator STANDARD_DECELERATE = new PathInterpolator(0f, 0f, 0f, 1f);
+ private static final Interpolator BACK_GESTURE = new BackGestureInterpolator();
private static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
0.05f, 0.7f, 0.1f, 1f);
private static final Interpolator STANDARD_ACCELERATE = new PathInterpolator(0.3f, 0f, 1f, 1f);
@@ -140,7 +141,7 @@ public class ImeBackAnimationController implements OnBackAnimationCallback {
float hiddenY = mWindowInsetsAnimationController.getHiddenStateInsets().bottom;
float shownY = mWindowInsetsAnimationController.getShownStateInsets().bottom;
float imeHeight = shownY - hiddenY;
- float interpolatedProgress = STANDARD_DECELERATE.getInterpolation(progress);
+ float interpolatedProgress = BACK_GESTURE.getInterpolation(progress);
int newY = (int) (imeHeight - interpolatedProgress * (imeHeight * PEEK_FRACTION));
mWindowInsetsAnimationController.setInsetsAndAlpha(Insets.of(0, 0, 0, newY), 1f,
progress);
diff --git a/core/java/android/view/animation/BackGestureInterpolator.java b/core/java/android/view/animation/BackGestureInterpolator.java
new file mode 100644
index 000000000000..c1595db98998
--- /dev/null
+++ b/core/java/android/view/animation/BackGestureInterpolator.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.animation;
+/**
+ * Decelerating interpolator with a very slight acceleration phase at the beginning.
+ * @hide
+ */
+public class BackGestureInterpolator extends PathInterpolator {
+ public BackGestureInterpolator() {
+ super(0.1f, 0.1f, 0f, 1f);
+ }
+}
diff --git a/core/java/android/window/RemoteTransitionStub.java b/core/java/android/window/RemoteTransitionStub.java
new file mode 100644
index 000000000000..c9932ab31469
--- /dev/null
+++ b/core/java/android/window/RemoteTransitionStub.java
@@ -0,0 +1,37 @@
+/*
+ * 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.window;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.SurfaceControl;
+
+/**
+ * Utility base implementation of {@link IRemoteTransition} that users can extend to avoid stubbing.
+ *
+ * @hide
+ */
+public abstract class RemoteTransitionStub extends IRemoteTransition.Stub {
+ @Override
+ public void mergeAnimation(IBinder transition, TransitionInfo info,
+ SurfaceControl.Transaction t, IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {}
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted)
+ throws RemoteException {}
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 5227724e705e..994e73288e93 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -538,6 +538,11 @@ public final class TransitionInfo implements Parcelable {
// If the change has no parent (it is root), then it is independent
if (change.getParent() == null) return true;
+ if (change.getLastParent() != null && !change.getLastParent().equals(change.getParent())) {
+ // If the change has been reparented, then it's independent.
+ return true;
+ }
+
// non-visibility changes will just be folded into the parent change, so they aren't
// independent either.
if (change.getMode() == TRANSIT_CHANGE) return false;
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 8c7b360a3fcd..97ce96ec30f6 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1054,8 +1054,7 @@ public class ParsingPackageUtils {
// An Apex package shouldn't have permission declarations
final boolean isApex = (flags & PARSE_APEX) != 0;
- if (android.permission.flags.Flags.ignoreApexPermissions()
- && isApex && !pkg.getPermissions().isEmpty()) {
+ if (isApex && !pkg.getPermissions().isEmpty()) {
return input.error(
INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
pkg.getPackageName()
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f5b1a47e917e..f931a762871c 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -28,6 +28,7 @@ import android.media.INearbyMediaDevicesProvider;
import android.media.MediaRoute2Info;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
import android.view.KeyEvent;
import android.service.notification.StatusBarNotification;
@@ -384,9 +385,11 @@ oneway interface IStatusBar
/**
* Shows the media output switcher dialog.
*
- * @param packageName of the session for which the output switcher is shown.
+ * @param targetPackageName The package name for which to show the output switcher.
+ * @param targetUserHandle The UserHandle on which the package for which to show the output
+ * switcher is running.
*/
- void showMediaOutputSwitcher(String packageName);
+ void showMediaOutputSwitcher(String targetPackageName, in UserHandle targetUserHandle);
/** Enters desktop mode from the current focused app.
*
diff --git a/core/res/res/values-mcc404/config.xml b/core/res/res/values-mcc404/config.xml
index 4cadef7893d3..0cb1029626b1 100644
--- a/core/res/res/values-mcc404/config.xml
+++ b/core/res/res/values-mcc404/config.xml
@@ -18,8 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Whether camera shutter sound is forced or not (country specific). -->
- <bool name="config_camera_sound_forced">true</bool>
<!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
<bool name="config_showAreaUpdateInfoSettings">true</bool>
</resources>
diff --git a/core/res/res/values-mcc405/config.xml b/core/res/res/values-mcc405/config.xml
index 4cadef7893d3..0cb1029626b1 100644
--- a/core/res/res/values-mcc405/config.xml
+++ b/core/res/res/values-mcc405/config.xml
@@ -18,8 +18,6 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- Whether camera shutter sound is forced or not (country specific). -->
- <bool name="config_camera_sound_forced">true</bool>
<!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
<bool name="config_showAreaUpdateInfoSettings">true</bool>
</resources>
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 2ce7a7d3d70d..a0aff6e7b9a0 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -16,7 +16,6 @@
package android.app.servertransaction;
-import static android.content.Context.DEVICE_ID_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertEquals;
@@ -29,9 +28,6 @@ import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.ClientTransactionHandler;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.RemoteException;
@@ -107,35 +103,6 @@ public class ClientTransactionItemTest {
}
@Test
- public void testActivityConfigurationChangeItem_getContextToUpdate() {
- final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
- .obtain(mActivityToken, mConfiguration, mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mActivity, context);
- }
-
- @Test
- public void testActivityRelaunchItem_getContextToUpdate() {
- final ActivityRelaunchItem item = ActivityRelaunchItem
- .obtain(mActivityToken, null /* pendingResults */, null /* pendingNewIntents */,
- 0 /* configChange */, mMergedConfiguration, false /* preserveWindow */,
- mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mActivity, context);
- }
-
- @Test
- public void testConfigurationChangeItem_getContextToUpdate() {
- final ConfigurationChangeItem item = ConfigurationChangeItem
- .obtain(mConfiguration, DEVICE_ID_DEFAULT);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(ActivityThread.currentApplication(), context);
- }
-
- @Test
public void testDestroyActivityItem_preExecute() {
final DestroyActivityItem item = DestroyActivityItem
.obtain(mActivityToken, false /* finished */);
@@ -166,26 +133,6 @@ public class ClientTransactionItemTest {
}
@Test
- public void testLaunchActivityItem_getContextToUpdate() {
- final LaunchActivityItem item = new TestUtils.LaunchActivityItemBuilder(
- mActivityToken, new Intent(), new ActivityInfo())
- .build();
-
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(ActivityThread.currentApplication(), context);
- }
-
- @Test
- public void testMoveToDisplayItem_getContextToUpdate() {
- final MoveToDisplayItem item = MoveToDisplayItem
- .obtain(mActivityToken, DEFAULT_DISPLAY, mConfiguration, mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mActivity, context);
- }
-
- @Test
public void testWindowContextInfoChangeItem_execute() {
final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
.obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
@@ -196,17 +143,6 @@ public class ClientTransactionItemTest {
}
@Test
- public void testWindowContextInfoChangeItem_getContextToUpdate() {
- doReturn(mWindowContext).when(mHandler).getWindowContext(mWindowClientToken);
-
- final WindowContextInfoChangeItem item = WindowContextInfoChangeItem
- .obtain(mWindowClientToken, mConfiguration, DEFAULT_DISPLAY);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(mWindowContext, context);
- }
-
- @Test
public void testWindowContextWindowRemovalItem_execute() {
final WindowContextWindowRemovalItem item = WindowContextWindowRemovalItem.obtain(
mWindowClientToken);
@@ -220,7 +156,7 @@ public class ClientTransactionItemTest {
final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
- true /* dragResizing */, mActivityToken, mActivityWindowInfo);
+ true /* dragResizing */, mActivityWindowInfo);
item.execute(mHandler, mPendingActions);
verify(mWindow).resized(mFrames,
@@ -228,16 +164,4 @@ public class ClientTransactionItemTest {
true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
true /* dragResizing */, mActivityWindowInfo);
}
-
- @Test
- public void testWindowStateResizeItem_getContextToUpdate() {
- final WindowStateResizeItem item = WindowStateResizeItem.obtain(mWindow, mFrames,
- true /* reportDraw */, mMergedConfiguration, mInsetsState, true /* forceLayout */,
- true /* alwaysConsumeSystemBars */, 123 /* displayId */, 321 /* syncSeqId */,
- true /* dragResizing */, mActivityToken, mActivityWindowInfo);
- final Context context = item.getContextToUpdate(mHandler);
-
- assertEquals(ActivityThread.currentApplication(), context);
- }
-
}
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 442394e3428a..0697c96052f6 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -16,6 +16,8 @@
package android.os;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
@@ -24,6 +26,7 @@ import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.annotations.Presubmit;
import android.platform.test.ravenwood.RavenwoodRule;
+import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
@@ -31,6 +34,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class ParcelTest {
@@ -349,6 +354,50 @@ public class ParcelTest {
}
@Test
+ public void testClassCookies() {
+ Parcel p = Parcel.obtain();
+ assertThat(p.hasClassCookie(ParcelTest.class)).isFalse();
+
+ p.setClassCookie(ParcelTest.class, "string_cookie");
+ assertThat(p.hasClassCookie(ParcelTest.class)).isTrue();
+ assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo("string_cookie");
+
+ p.removeClassCookie(ParcelTest.class, "string_cookie");
+ assertThat(p.hasClassCookie(ParcelTest.class)).isFalse();
+ assertThat(p.getClassCookie(ParcelTest.class)).isEqualTo(null);
+
+ p.setClassCookie(ParcelTest.class, "to_be_discarded_cookie");
+ p.recycle();
+ assertThat(p.getClassCookie(ParcelTest.class)).isNull();
+ }
+
+ @Test
+ public void testClassCookies_removeUnexpected() {
+ Parcel p = Parcel.obtain();
+
+ assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "not_present"));
+
+ p.setClassCookie(ParcelTest.class, "value");
+
+ assertLogsWtf(() -> p.removeClassCookie(ParcelTest.class, "different"));
+ assertThat(p.getClassCookie(ParcelTest.class)).isNull(); // still removed
+
+ p.recycle();
+ }
+
+ private static void assertLogsWtf(Runnable test) {
+ ArrayList<Log.TerribleFailure> wtfs = new ArrayList<>();
+ Log.TerribleFailureHandler oldHandler = Log.setWtfHandler(
+ (tag, what, system) -> wtfs.add(what));
+ try {
+ test.run();
+ } finally {
+ Log.setWtfHandler(oldHandler);
+ }
+ assertThat(wtfs).hasSize(1);
+ }
+
+ @Test
@IgnoreUnderRavenwood(blockedBy = Parcel.class)
public void testHasBinders_AfterWritingBinderToParcel() {
Binder binder = new Binder();
@@ -360,7 +409,6 @@ public class ParcelTest {
assertTrue(pA.hasBinders());
}
-
@Test
@IgnoreUnderRavenwood(blockedBy = Parcel.class)
public void testHasBindersInRange_AfterWritingBinderToParcel() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index 19963675ff86..ce0bf8b29374 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.animation;
import android.graphics.Path;
+import android.view.animation.BackGestureInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
@@ -95,6 +96,15 @@ public class Interpolators {
public static final PathInterpolator DIM_INTERPOLATOR =
new PathInterpolator(.23f, .87f, .52f, -0.11f);
+ /**
+ * Use this interpolator for animating progress values coming from the back callback to get
+ * the predictive-back-typical decelerate motion.
+ *
+ * This interpolator is similar to {@link Interpolators#STANDARD_DECELERATE} but has a slight
+ * acceleration phase at the start.
+ */
+ public static final Interpolator BACK_GESTURE = new BackGestureInterpolator();
+
// Create the default emphasized interpolator
private static PathInterpolator createEmphasizedInterpolator() {
Path path = new Path();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index 112ed0941122..7cb56605cc12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -87,7 +87,7 @@ class CrossActivityBackAnimation @Inject constructor(
private val enteringStartOffset =
context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
- private val gestureInterpolator = Interpolators.STANDARD_DECELERATE
+ private val gestureInterpolator = Interpolators.BACK_GESTURE
private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
private val verticalMoveInterpolator: Interpolator = DecelerateInterpolator()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index c34f30df33a2..ee898a73a291 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -93,7 +93,7 @@ public class CrossTaskBackAnimation extends ShellBackAnimation {
private final PointF mInitialTouchPos = new PointF();
private final Interpolator mPostAnimationInterpolator = Interpolators.EMPHASIZED;
- private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
+ private final Interpolator mProgressInterpolator = Interpolators.BACK_GESTURE;
private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final Matrix mTransformMatrix = new Matrix();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
index 00daddc13346..7a6032c60cce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -28,7 +28,7 @@ public class ShellBackAnimationRegistry {
private static final String TAG = "ShellBackPreview";
private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
- private final ShellBackAnimation mDefaultCrossActivityAnimation;
+ private ShellBackAnimation mDefaultCrossActivityAnimation;
private final ShellBackAnimation mCustomizeActivityAnimation;
private final ShellBackAnimation mCrossTaskAnimation;
@@ -67,10 +67,18 @@ public class ShellBackAnimationRegistry {
void registerAnimation(
@BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) {
mAnimationDefinition.set(type, runner);
+ // Only happen in test
+ if (BackNavigationInfo.TYPE_CROSS_ACTIVITY == type) {
+ mDefaultCrossActivityAnimation = null;
+ }
}
void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
mAnimationDefinition.remove(type);
+ // Only happen in test
+ if (BackNavigationInfo.TYPE_CROSS_ACTIVITY == type) {
+ mDefaultCrossActivityAnimation = null;
+ }
}
/**
@@ -129,9 +137,15 @@ public class ShellBackAnimationRegistry {
}
void onConfigurationChanged(Configuration newConfig) {
- mCustomizeActivityAnimation.onConfigurationChanged(newConfig);
- mDefaultCrossActivityAnimation.onConfigurationChanged(newConfig);
- mCrossTaskAnimation.onConfigurationChanged(newConfig);
+ if (mCustomizeActivityAnimation != null) {
+ mCustomizeActivityAnimation.onConfigurationChanged(newConfig);
+ }
+ if (mDefaultCrossActivityAnimation != null) {
+ mDefaultCrossActivityAnimation.onConfigurationChanged(newConfig);
+ }
+ if (mCrossTaskAnimation != null) {
+ mCrossTaskAnimation.onConfigurationChanged(newConfig);
+ }
}
BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
index 987aadfbdef2..74499c7e429e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MoveToDesktopAnimator.kt
@@ -33,6 +33,7 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
get() = dragToDesktopAnimator.animatedValue as Float * startBounds.width()
val scale: Float
get() = dragToDesktopAnimator.animatedValue as Float
+ private val mostRecentInput = PointF()
private val dragToDesktopAnimator: ValueAnimator = ValueAnimator.ofFloat(1f,
DRAG_FREEFORM_SCALE)
.setDuration(ANIMATION_DURATION.toLong())
@@ -40,9 +41,13 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
val t = SurfaceControl.Transaction()
val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
addUpdateListener {
+ setTaskPosition(mostRecentInput.x, mostRecentInput.y)
t.setScale(taskSurface, scale, scale)
- .setCornerRadius(taskSurface, cornerRadius)
- .apply()
+ .setCornerRadius(taskSurface, cornerRadius)
+ .setScale(taskSurface, scale, scale)
+ .setCornerRadius(taskSurface, cornerRadius)
+ .setPosition(taskSurface, position.x, position.y)
+ .apply()
}
}
@@ -78,19 +83,28 @@ class MoveToDesktopAnimator @JvmOverloads constructor(
// allow dragging beyond its stage across any region of the display. Because of that, the
// rawX/Y are more true to where the gesture is on screen and where the surface should be
// positioned.
- position.x = ev.rawX - animatedTaskWidth / 2
- position.y = ev.rawY
+ mostRecentInput.set(ev.rawX, ev.rawY)
- if (!allowSurfaceChangesOnMove) {
+ // If animator is running, allow it to set scale and position at the same time.
+ if (!allowSurfaceChangesOnMove || dragToDesktopAnimator.isRunning) {
return
}
-
+ setTaskPosition(ev.rawX, ev.rawY)
val t = transactionFactory()
t.setPosition(taskSurface, position.x, position.y)
t.apply()
}
/**
+ * Calculates the top left corner of task from input coordinates.
+ * Top left will be needed for the resulting surface control transaction.
+ */
+ private fun setTaskPosition(x: Float, y: Float) {
+ position.x = x - animatedTaskWidth / 2
+ position.y = y
+ }
+
+ /**
* Cancels the animation, intended to be used when another animator will take over.
*/
fun cancelAnimator() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index e9da25813510..2366917a0158 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -83,6 +83,7 @@ import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.IWindowContainerToken;
import android.window.RemoteTransition;
+import android.window.RemoteTransitionStub;
import android.window.TransitionFilter;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -280,7 +281,7 @@ public class ShellTransitionTests extends ShellTestCase {
final boolean[] remoteCalled = new boolean[]{false};
final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction();
- IRemoteTransition testRemote = new IRemoteTransition.Stub() {
+ IRemoteTransition testRemote = new RemoteTransitionStub() {
@Override
public void startAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t,
@@ -288,16 +289,6 @@ public class ShellTransitionTests extends ShellTestCase {
remoteCalled[0] = true;
finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */);
}
-
- @Override
- public void mergeAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
- }
-
- @Override
- public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
- }
};
IBinder transitToken = new Binder();
transitions.requestStartTransition(transitToken,
@@ -450,7 +441,7 @@ public class ShellTransitionTests extends ShellTestCase {
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
final boolean[] remoteCalled = new boolean[]{false};
- IRemoteTransition testRemote = new IRemoteTransition.Stub() {
+ IRemoteTransition testRemote = new RemoteTransitionStub() {
@Override
public void startAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t,
@@ -458,16 +449,6 @@ public class ShellTransitionTests extends ShellTestCase {
remoteCalled[0] = true;
finishCallback.onTransitionFinished(null /* wct */, null /* sct */);
}
-
- @Override
- public void mergeAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
- }
-
- @Override
- public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
- }
};
TransitionFilter filter = new TransitionFilter();
@@ -500,7 +481,7 @@ public class ShellTransitionTests extends ShellTestCase {
final boolean[] remoteCalled = new boolean[]{false};
final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction();
- IRemoteTransition testRemote = new IRemoteTransition.Stub() {
+ IRemoteTransition testRemote = new RemoteTransitionStub() {
@Override
public void startAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t,
@@ -508,16 +489,6 @@ public class ShellTransitionTests extends ShellTestCase {
remoteCalled[0] = true;
finishCallback.onTransitionFinished(remoteFinishWCT, null /* sct */);
}
-
- @Override
- public void mergeAnimation(IBinder token, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
- }
-
- @Override
- public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
- }
};
final int transitType = TRANSIT_FIRST_CUSTOM + 1;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
index 87330d2dc877..184e8955d08c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/TestRemoteTransition.java
@@ -20,6 +20,7 @@ import android.os.RemoteException;
import android.view.SurfaceControl;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransitionStub;
import android.window.TransitionInfo;
import android.window.WindowContainerTransaction;
@@ -29,7 +30,7 @@ import android.window.WindowContainerTransaction;
* {@link #startAnimation(IBinder, TransitionInfo, SurfaceControl.Transaction,
* IRemoteTransitionFinishedCallback)} being called.
*/
-public class TestRemoteTransition extends IRemoteTransition.Stub {
+public class TestRemoteTransition extends RemoteTransitionStub {
private boolean mCalled = false;
private boolean mConsumed = false;
final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction();
@@ -44,12 +45,6 @@ public class TestRemoteTransition extends IRemoteTransition.Stub {
}
@Override
- public void mergeAnimation(IBinder transition, TransitionInfo info,
- SurfaceControl.Transaction t, IBinder mergeTarget,
- IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
- }
-
- @Override
public void onTransitionConsumed(IBinder iBinder, boolean b) throws RemoteException {
mConsumed = true;
}
diff --git a/libs/hostgraphics/ANativeWindow.cpp b/libs/hostgraphics/ANativeWindow.cpp
new file mode 100644
index 000000000000..fcfaf0235293
--- /dev/null
+++ b/libs/hostgraphics/ANativeWindow.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+#include <system/window.h>
+
+static int32_t query(ANativeWindow* window, int what) {
+ int value;
+ int res = window->query(window, what, &value);
+ return res < 0 ? res : value;
+}
+
+static int64_t query64(ANativeWindow* window, int what) {
+ int64_t value;
+ int res = window->perform(window, what, &value);
+ return res < 0 ? res : value;
+}
+
+int ANativeWindow_setCancelBufferInterceptor(ANativeWindow* window,
+ ANativeWindow_cancelBufferInterceptor interceptor,
+ void* data) {
+ return window->perform(window, NATIVE_WINDOW_SET_CANCEL_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_setDequeueBufferInterceptor(ANativeWindow* window,
+ ANativeWindow_dequeueBufferInterceptor interceptor,
+ void* data) {
+ return window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_setQueueBufferInterceptor(ANativeWindow* window,
+ ANativeWindow_queueBufferInterceptor interceptor,
+ void* data) {
+ return window->perform(window, NATIVE_WINDOW_SET_QUEUE_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_setPerformInterceptor(ANativeWindow* window,
+ ANativeWindow_performInterceptor interceptor, void* data) {
+ return window->perform(window, NATIVE_WINDOW_SET_PERFORM_INTERCEPTOR, interceptor, data);
+}
+
+int ANativeWindow_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer, int* fenceFd) {
+ return window->dequeueBuffer(window, buffer, fenceFd);
+}
+
+int ANativeWindow_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer, int fenceFd) {
+ return window->cancelBuffer(window, buffer, fenceFd);
+}
+
+int ANativeWindow_setDequeueTimeout(ANativeWindow* window, int64_t timeout) {
+ return window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT, timeout);
+}
+
+// extern "C", so that it can be used outside libhostgraphics (in host hwui/.../CanvasContext.cpp)
+extern "C" void ANativeWindow_tryAllocateBuffers(ANativeWindow* window) {
+ if (!window || !query(window, NATIVE_WINDOW_IS_VALID)) {
+ return;
+ }
+ window->perform(window, NATIVE_WINDOW_ALLOCATE_BUFFERS);
+}
+
+int64_t ANativeWindow_getLastDequeueStartTime(ANativeWindow* window) {
+ return query64(window, NATIVE_WINDOW_GET_LAST_DEQUEUE_START);
+}
+
+int64_t ANativeWindow_getLastDequeueDuration(ANativeWindow* window) {
+ return query64(window, NATIVE_WINDOW_GET_LAST_DEQUEUE_DURATION);
+}
+
+int64_t ANativeWindow_getLastQueueDuration(ANativeWindow* window) {
+ return query64(window, NATIVE_WINDOW_GET_LAST_QUEUE_DURATION);
+}
+
+int32_t ANativeWindow_getWidth(ANativeWindow* window) {
+ return query(window, NATIVE_WINDOW_WIDTH);
+}
+
+int32_t ANativeWindow_getHeight(ANativeWindow* window) {
+ return query(window, NATIVE_WINDOW_HEIGHT);
+}
+
+int32_t ANativeWindow_getFormat(ANativeWindow* window) {
+ return query(window, NATIVE_WINDOW_FORMAT);
+}
+
+void ANativeWindow_acquire(ANativeWindow* window) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ window->incStrong((void*)ANativeWindow_acquire);
+}
+
+void ANativeWindow_release(ANativeWindow* window) {
+ // incStrong/decStrong token must be the same, doesn't matter what it is
+ window->decStrong((void*)ANativeWindow_acquire);
+}
diff --git a/libs/hostgraphics/Android.bp b/libs/hostgraphics/Android.bp
index 4407af68de99..09232b64616d 100644
--- a/libs/hostgraphics/Android.bp
+++ b/libs/hostgraphics/Android.bp
@@ -17,26 +17,18 @@ cc_library_host_static {
static_libs: [
"libbase",
"libmath",
+ "libui-types",
"libutils",
],
srcs: [
- ":libui_host_common",
"ADisplay.cpp",
+ "ANativeWindow.cpp",
"Fence.cpp",
"HostBufferQueue.cpp",
"PublicFormat.cpp",
],
- include_dirs: [
- // Here we override all the headers automatically included with frameworks/native/include.
- // When frameworks/native/include will be removed from the list of automatic includes.
- // We will have to copy necessary headers with a pre-build step (generated headers).
- ".",
- "frameworks/native/libs/arect/include",
- "frameworks/native/libs/ui/include_private",
- ],
-
header_libs: [
"libnativebase_headers",
"libnativedisplay_headers",
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4706699039a6..7439fbc1149c 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -96,6 +96,7 @@ cc_defaults {
],
cflags: [
"-Wno-unused-variable",
+ "-D__INTRODUCED_IN(n)=",
],
},
},
@@ -539,7 +540,10 @@ cc_defaults {
"pipeline/skia/ReorderBarrierDrawables.cpp",
"pipeline/skia/TransformCanvas.cpp",
"renderstate/RenderState.cpp",
+ "renderthread/CanvasContext.cpp",
+ "renderthread/DrawFrameTask.cpp",
"renderthread/Frame.cpp",
+ "renderthread/RenderProxy.cpp",
"renderthread/RenderTask.cpp",
"renderthread/TimeLord.cpp",
"hwui/AnimatedImageDrawable.cpp",
@@ -589,6 +593,7 @@ cc_defaults {
"SkiaCanvas.cpp",
"SkiaInterpolator.cpp",
"Tonemapper.cpp",
+ "TreeInfo.cpp",
"VectorDrawable.cpp",
],
@@ -617,14 +622,11 @@ cc_defaults {
"pipeline/skia/VkFunctorDrawable.cpp",
"pipeline/skia/VkInteropFunctorDrawable.cpp",
"renderthread/CacheManager.cpp",
- "renderthread/CanvasContext.cpp",
- "renderthread/DrawFrameTask.cpp",
"renderthread/EglManager.cpp",
"renderthread/ReliableSurface.cpp",
"renderthread/RenderEffectCapabilityQuery.cpp",
"renderthread/VulkanManager.cpp",
"renderthread/VulkanSurface.cpp",
- "renderthread/RenderProxy.cpp",
"renderthread/RenderThread.cpp",
"renderthread/HintSessionWrapper.cpp",
"service/GraphicsStatsService.cpp",
@@ -636,7 +638,6 @@ cc_defaults {
"Layer.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
- "TreeInfo.cpp",
"WebViewFunctorManager.cpp",
"protos/graphicsstats.proto",
],
@@ -654,6 +655,8 @@ cc_defaults {
srcs: [
"platform/host/renderthread/CacheManager.cpp",
+ "platform/host/renderthread/HintSessionWrapper.cpp",
+ "platform/host/renderthread/ReliableSurface.cpp",
"platform/host/renderthread/RenderThread.cpp",
"platform/host/ProfileDataContainer.cpp",
"platform/host/Readback.cpp",
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index f526a280b113..589abb4d87f4 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -16,18 +16,6 @@
#include "RenderNode.h"
-#include "DamageAccumulator.h"
-#include "Debug.h"
-#include "Properties.h"
-#include "TreeInfo.h"
-#include "VectorDrawable.h"
-#include "private/hwui/WebViewFunctor.h"
-#ifdef __ANDROID__
-#include "renderthread/CanvasContext.h"
-#else
-#include "DamageAccumulator.h"
-#include "pipeline/skia/SkiaDisplayList.h"
-#endif
#include <SkPathOps.h>
#include <gui/TraceUtils.h>
#include <ui/FatVector.h>
@@ -37,6 +25,14 @@
#include <sstream>
#include <string>
+#include "DamageAccumulator.h"
+#include "Debug.h"
+#include "Properties.h"
+#include "TreeInfo.h"
+#include "VectorDrawable.h"
+#include "private/hwui/WebViewFunctor.h"
+#include "renderthread/CanvasContext.h"
+
#ifdef __ANDROID__
#include "include/gpu/ganesh/SkImageGanesh.h"
#endif
@@ -186,7 +182,6 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) {
}
void RenderNode::pushLayerUpdate(TreeInfo& info) {
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext and Layers
LayerType layerType = properties().effectiveLayerType();
// If we are not a layer OR we cannot be rendered (eg, view was detached)
// we need to destroy any Layers we may have had previously
@@ -218,7 +213,6 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) {
// That might be us, so tell CanvasContext that this layer is in the
// tree and should not be destroyed.
info.canvasContext.markLayerInUse(this);
-#endif
}
/**
diff --git a/libs/hwui/RootRenderNode.cpp b/libs/hwui/RootRenderNode.cpp
index ddbbf58b3071..5174e27ae587 100644
--- a/libs/hwui/RootRenderNode.cpp
+++ b/libs/hwui/RootRenderNode.cpp
@@ -18,11 +18,12 @@
#ifdef __ANDROID__ // Layoutlib does not support Looper (windows)
#include <utils/Looper.h>
+#else
+#include "utils/MessageHandler.h"
#endif
namespace android::uirenderer {
-#ifdef __ANDROID__ // Layoutlib does not support Looper
class FinishAndInvokeListener : public MessageHandler {
public:
explicit FinishAndInvokeListener(PropertyValuesAnimatorSet* anim) : mAnimator(anim) {
@@ -237,9 +238,13 @@ void RootRenderNode::detachVectorDrawableAnimator(PropertyValuesAnimatorSet* ani
// user events, in which case the already posted listener's id will become stale, and
// the onFinished callback will then be ignored.
sp<FinishAndInvokeListener> message = new FinishAndInvokeListener(anim);
+#ifdef __ANDROID__ // Layoutlib does not support Looper
auto looper = Looper::getForThread();
LOG_ALWAYS_FATAL_IF(looper == nullptr, "Not on a looper thread?");
looper->sendMessageDelayed(ms2ns(remainingTimeInMs), message, 0);
+#else
+ message->handleMessage(0);
+#endif
anim->clearOneShotListener();
}
}
@@ -285,22 +290,5 @@ private:
AnimationContext* ContextFactoryImpl::createAnimationContext(renderthread::TimeLord& clock) {
return new AnimationContextBridge(clock, mRootNode);
}
-#else
-
-void RootRenderNode::prepareTree(TreeInfo& info) {
- info.errorHandler = mErrorHandler.get();
- info.updateWindowPositions = true;
- RenderNode::prepareTree(info);
- info.updateWindowPositions = false;
- info.errorHandler = nullptr;
-}
-
-void RootRenderNode::attachAnimatingNode(RenderNode* animatingNode) { }
-
-void RootRenderNode::destroy() { }
-
-void RootRenderNode::addVectorDrawableAnimator(PropertyValuesAnimatorSet* anim) { }
-
-#endif
} // namespace android::uirenderer
diff --git a/libs/hwui/RootRenderNode.h b/libs/hwui/RootRenderNode.h
index 1d3f5a8a51e0..7a5cda7041ed 100644
--- a/libs/hwui/RootRenderNode.h
+++ b/libs/hwui/RootRenderNode.h
@@ -74,7 +74,6 @@ private:
void detachVectorDrawableAnimator(PropertyValuesAnimatorSet* anim);
};
-#ifdef __ANDROID__ // Layoutlib does not support Animations
class ContextFactoryImpl : public IContextFactory {
public:
explicit ContextFactoryImpl(RootRenderNode* rootNode) : mRootNode(rootNode) {}
@@ -84,6 +83,5 @@ public:
private:
RootRenderNode* mRootNode;
};
-#endif
} // namespace android::uirenderer
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index efa9b1174a3a..9d16ee86739e 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -87,7 +87,7 @@ void WebViewFunctor_release(int functor) {
WebViewFunctorManager::instance().releaseFunctor(functor);
}
-void WebViewFunctor_reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size) {
+void WebViewFunctor_reportRenderingThreads(int functor, const pid_t* thread_ids, size_t size) {
WebViewFunctorManager::instance().reportRenderingThreads(functor, thread_ids, size);
}
@@ -265,8 +265,8 @@ void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) {
funcs.transactionDeleteFunc(transaction);
}
-void WebViewFunctor::reportRenderingThreads(const int32_t* thread_ids, size_t size) {
- mRenderingThreads = std::vector<int32_t>(thread_ids, thread_ids + size);
+void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) {
+ mRenderingThreads = std::vector<pid_t>(thread_ids, thread_ids + size);
}
WebViewFunctorManager& WebViewFunctorManager::instance() {
@@ -355,7 +355,7 @@ void WebViewFunctorManager::destroyFunctor(int functor) {
}
}
-void WebViewFunctorManager::reportRenderingThreads(int functor, const int32_t* thread_ids,
+void WebViewFunctorManager::reportRenderingThreads(int functor, const pid_t* thread_ids,
size_t size) {
std::lock_guard _lock{mLock};
for (auto& iter : mFunctors) {
@@ -366,8 +366,8 @@ void WebViewFunctorManager::reportRenderingThreads(int functor, const int32_t* t
}
}
-std::vector<int32_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
- std::vector<int32_t> renderingThreads;
+std::vector<pid_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
+ std::vector<pid_t> renderingThreads;
std::lock_guard _lock{mLock};
for (const auto& iter : mActiveFunctors) {
const auto& functorThreads = iter->getRenderingThreads();
diff --git a/libs/hwui/WebViewFunctorManager.h b/libs/hwui/WebViewFunctorManager.h
index 2d77dd8d09bc..ec17640f9b5e 100644
--- a/libs/hwui/WebViewFunctorManager.h
+++ b/libs/hwui/WebViewFunctorManager.h
@@ -17,13 +17,11 @@
#pragma once
#include <private/hwui/WebViewFunctor.h>
-#ifdef __ANDROID__ // Layoutlib does not support render thread
#include <renderthread/RenderProxy.h>
-#endif
-
#include <utils/LightRefBase.h>
#include <utils/Log.h>
#include <utils/StrongPointer.h>
+
#include <mutex>
#include <vector>
@@ -38,11 +36,7 @@ public:
class Handle : public LightRefBase<Handle> {
public:
- ~Handle() {
-#ifdef __ANDROID__ // Layoutlib does not support render thread
- renderthread::RenderProxy::destroyFunctor(id());
-#endif
- }
+ ~Handle() { renderthread::RenderProxy::destroyFunctor(id()); }
int id() const { return mReference.id(); }
@@ -60,7 +54,7 @@ public:
void onRemovedFromTree() { mReference.onRemovedFromTree(); }
- const std::vector<int32_t>& getRenderingThreads() const {
+ const std::vector<pid_t>& getRenderingThreads() const {
return mReference.getRenderingThreads();
}
@@ -85,8 +79,8 @@ public:
ASurfaceControl* getSurfaceControl();
void mergeTransaction(ASurfaceTransaction* transaction);
- void reportRenderingThreads(const int32_t* thread_ids, size_t size);
- const std::vector<int32_t>& getRenderingThreads() const { return mRenderingThreads; }
+ void reportRenderingThreads(const pid_t* thread_ids, size_t size);
+ const std::vector<pid_t>& getRenderingThreads() const { return mRenderingThreads; }
sp<Handle> createHandle() {
LOG_ALWAYS_FATAL_IF(mCreatedHandle);
@@ -107,7 +101,7 @@ private:
bool mCreatedHandle = false;
int32_t mParentSurfaceControlGenerationId = 0;
ASurfaceControl* mSurfaceControl = nullptr;
- std::vector<int32_t> mRenderingThreads;
+ std::vector<pid_t> mRenderingThreads;
};
class WebViewFunctorManager {
@@ -118,8 +112,8 @@ public:
void releaseFunctor(int functor);
void onContextDestroyed();
void destroyFunctor(int functor);
- void reportRenderingThreads(int functor, const int32_t* thread_ids, size_t size);
- std::vector<int32_t> getRenderingThreadsForActiveFunctors();
+ void reportRenderingThreads(int functor, const pid_t* thread_ids, size_t size);
+ std::vector<pid_t> getRenderingThreadsForActiveFunctors();
sp<WebViewFunctor::Handle> handleFor(int functor);
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index 770822a049b7..fd9915a54bb5 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -164,8 +164,10 @@ static vector<string> parseCsv(JNIEnv* env, jstring csvJString) {
} // namespace android
using namespace android;
+using namespace android::uirenderer;
void init_android_graphics() {
+ Properties::overrideRenderPipelineType(RenderPipelineType::SkiaCpu);
SkGraphics::Init();
}
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index 0f80c55d0ed0..b01e38d014a9 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -27,6 +27,8 @@
#include <hwui/ImageDecoder.h>
#ifdef __ANDROID__
#include <utils/Looper.h>
+#else
+#include "utils/MessageHandler.h"
#endif
#include "ColorFilter.h"
@@ -182,23 +184,6 @@ static void AnimatedImageDrawable_nSetRepeatCount(JNIEnv* env, jobject /*clazz*/
drawable->setRepetitionCount(loopCount);
}
-#ifndef __ANDROID__
-struct Message {
- Message(int w) {}
-};
-
-class MessageHandler : public virtual RefBase {
-protected:
- virtual ~MessageHandler() override {}
-
-public:
- /**
- * Handles a message.
- */
- virtual void handleMessage(const Message& message) = 0;
-};
-#endif
-
class InvokeListener : public MessageHandler {
public:
InvokeListener(JNIEnv* env, jobject javaObject) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 9e21f860ce21..d4157008ca46 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,8 +1,14 @@
// #define LOG_NDEBUG 0
#include "Bitmap.h"
+#include <android-base/unique_fd.h>
#include <hwui/Bitmap.h>
#include <hwui/Paint.h>
+#include <inttypes.h>
+#include <renderthread/RenderProxy.h>
+#include <string.h>
+
+#include <memory>
#include "CreateJavaOutputStreamAdaptor.h"
#include "Gainmap.h"
@@ -24,16 +30,6 @@
#include "SkTypes.h"
#include "android_nio_utils.h"
-#ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
-#include <android-base/unique_fd.h>
-#include <renderthread/RenderProxy.h>
-#endif
-
-#include <inttypes.h>
-#include <string.h>
-
-#include <memory>
-
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
@@ -1105,11 +1101,9 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, jlong bm1Ha
}
static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapPtr) {
-#ifdef __ANDROID__ // Layoutlib does not support render thread
LocalScopedBitmap bitmapHandle(bitmapPtr);
if (!bitmapHandle.valid()) return;
android::uirenderer::renderthread::RenderProxy::prepareToDraw(bitmapHandle->bitmap());
-#endif
}
static jint Bitmap_getAllocationByteCount(JNIEnv* env, jobject, jlong bitmapPtr) {
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index 426644ee6a4e..948362c30a31 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -16,22 +16,19 @@
#include "GraphicsJNI.h"
-#ifdef __ANDROID__ // Layoutlib does not support Looper and device properties
+#ifdef __ANDROID__ // Layoutlib does not support Looper
#include <utils/Looper.h>
#endif
-#include <SkRegion.h>
-#include <SkRuntimeEffect.h>
-
+#include <CanvasProperty.h>
#include <Rect.h>
#include <RenderNode.h>
-#include <CanvasProperty.h>
+#include <SkRegion.h>
+#include <SkRuntimeEffect.h>
#include <hwui/Canvas.h>
#include <hwui/Paint.h>
#include <minikin/Layout.h>
-#ifdef __ANDROID__ // Layoutlib does not support RenderThread
#include <renderthread/RenderProxy.h>
-#endif
namespace android {
@@ -85,11 +82,7 @@ static void android_view_DisplayListCanvas_resetDisplayListCanvas(CRITICAL_JNI_P
}
static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) {
-#ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread)
return android::uirenderer::renderthread::RenderProxy::maxTextureSize();
-#else
- return 4096;
-#endif
}
static void android_view_DisplayListCanvas_enableZ(CRITICAL_JNI_PARAMS_COMMA jlong canvasPtr,
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index a7d64231da80..6e03bbd0fa16 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -15,19 +15,17 @@
*/
#define ATRACE_TAG ATRACE_TAG_VIEW
-#include "GraphicsJNI.h"
-
#include <Animator.h>
#include <DamageAccumulator.h>
#include <Matrix.h>
#include <RenderNode.h>
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
-#include <renderthread/CanvasContext.h>
-#endif
#include <TreeInfo.h>
#include <effects/StretchEffect.h>
#include <gui/TraceUtils.h>
#include <hwui/Paint.h>
+#include <renderthread/CanvasContext.h>
+
+#include "GraphicsJNI.h"
namespace android {
@@ -640,7 +638,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
ATRACE_NAME("Update SurfaceView position");
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
JNIEnv* env = jnienv();
// Update the new position synchronously. We cannot defer this to
// a worker pool to process asynchronously because the UI thread
@@ -669,7 +666,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
env->DeleteGlobalRef(mListener);
mListener = nullptr;
}
-#endif
}
virtual void onPositionLost(RenderNode& node, const TreeInfo* info) override {
@@ -682,7 +678,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
ATRACE_NAME("SurfaceView position lost");
JNIEnv* env = jnienv();
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
// Update the lost position synchronously. We cannot defer this to
// a worker pool to process asynchronously because the UI thread
// may be unblocked by the time a worker thread can process this,
@@ -698,7 +693,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
env->DeleteGlobalRef(mListener);
mListener = nullptr;
}
-#endif
}
private:
@@ -750,7 +744,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
StretchEffectBehavior::Shader) {
JNIEnv* env = jnienv();
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
SkVector stretchDirection = effect->getStretchDirection();
jboolean keepListening = env->CallStaticBooleanMethod(
gPositionListener.clazz, gPositionListener.callApplyStretch, mListener,
@@ -762,7 +755,6 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
env->DeleteGlobalRef(mListener);
mListener = nullptr;
}
-#endif
}
}
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index e0216b680064..36dc933aa7b0 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -15,22 +15,18 @@
*/
#include "SkiaDisplayList.h"
-#include "FunctorDrawable.h"
+#include <SkImagePriv.h>
+#include <SkPathOps.h>
+
+// clang-format off
+#include "FunctorDrawable.h" // Must be included before DumpOpsCanvas.h
#include "DumpOpsCanvas.h"
-#ifdef __ANDROID__ // Layoutlib does not support SkiaPipeline
+// clang-format on
#include "SkiaPipeline.h"
-#else
-#include "DamageAccumulator.h"
-#endif
#include "TreeInfo.h"
#include "VectorDrawable.h"
-#ifdef __ANDROID__
#include "renderthread/CanvasContext.h"
-#endif
-
-#include <SkImagePriv.h>
-#include <SkPathOps.h>
namespace android {
namespace uirenderer {
@@ -101,7 +97,6 @@ bool SkiaDisplayList::prepareListAndChildren(
// If the prepare tree is triggered by the UI thread and no previous call to
// pinImages has failed then we must pin all mutable images in the GPU cache
// until the next UI thread draw.
-#ifdef __ANDROID__ // Layoutlib does not support CanvasContext
if (info.prepareTextures && !info.canvasContext.pinImages(mMutableImages)) {
// In the event that pinning failed we prevent future pinImage calls for the
// remainder of this tree traversal and also unpin any currently pinned images
@@ -110,11 +105,11 @@ bool SkiaDisplayList::prepareListAndChildren(
info.canvasContext.unpinImages();
}
+#ifdef __ANDROID__
auto grContext = info.canvasContext.getGrContext();
for (const auto& bufferData : mMeshBufferData) {
bufferData->updateBuffers(grContext);
}
-
#endif
bool hasBackwardProjectedNodesHere = false;
diff --git a/libs/hwui/platform/host/WebViewFunctorManager.cpp b/libs/hwui/platform/host/WebViewFunctorManager.cpp
index 1d16655bf73c..4ba206b41b39 100644
--- a/libs/hwui/platform/host/WebViewFunctorManager.cpp
+++ b/libs/hwui/platform/host/WebViewFunctorManager.cpp
@@ -50,6 +50,8 @@ ASurfaceControl* WebViewFunctor::getSurfaceControl() {
void WebViewFunctor::mergeTransaction(ASurfaceTransaction* transaction) {}
+void WebViewFunctor::reportRenderingThreads(const pid_t* thread_ids, size_t size) {}
+
void WebViewFunctor::reparentSurfaceControl(ASurfaceControl* parent) {}
WebViewFunctorManager& WebViewFunctorManager::instance() {
@@ -68,6 +70,13 @@ void WebViewFunctorManager::onContextDestroyed() {}
void WebViewFunctorManager::destroyFunctor(int functor) {}
+void WebViewFunctorManager::reportRenderingThreads(int functor, const pid_t* thread_ids,
+ size_t size) {}
+
+std::vector<pid_t> WebViewFunctorManager::getRenderingThreadsForActiveFunctors() {
+ return {};
+}
+
sp<WebViewFunctor::Handle> WebViewFunctorManager::handleFor(int functor) {
return nullptr;
}
diff --git a/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp b/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp
new file mode 100644
index 000000000000..b1b1d5830834
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/HintSessionWrapper.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "renderthread/HintSessionWrapper.h"
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+void HintSessionWrapper::HintSessionBinding::init() {}
+
+HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
+ : mUiThreadId(uiThreadId)
+ , mRenderThreadId(renderThreadId)
+ , mBinding(std::make_shared<HintSessionBinding>()) {}
+
+HintSessionWrapper::~HintSessionWrapper() {}
+
+void HintSessionWrapper::destroy() {}
+
+bool HintSessionWrapper::init() {
+ return false;
+}
+
+void HintSessionWrapper::updateTargetWorkDuration(long targetWorkDurationNanos) {}
+
+void HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {}
+
+void HintSessionWrapper::sendLoadResetHint() {}
+
+void HintSessionWrapper::sendLoadIncreaseHint() {}
+
+bool HintSessionWrapper::alive() {
+ return false;
+}
+
+nsecs_t HintSessionWrapper::getLastUpdate() {
+ return -1;
+}
+
+void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay,
+ std::shared_ptr<HintSessionWrapper> wrapperPtr) {}
+
+void HintSessionWrapper::setActiveFunctorThreads(std::vector<pid_t> threadIds) {}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/platform/host/renderthread/ReliableSurface.cpp b/libs/hwui/platform/host/renderthread/ReliableSurface.cpp
new file mode 100644
index 000000000000..2deaaf3b909c
--- /dev/null
+++ b/libs/hwui/platform/host/renderthread/ReliableSurface.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "renderthread/ReliableSurface.h"
+
+#include <log/log_main.h>
+#include <system/window.h>
+
+namespace android::uirenderer::renderthread {
+
+ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) {
+ LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr");
+ ANativeWindow_acquire(mWindow);
+}
+
+ReliableSurface::~ReliableSurface() {
+ ANativeWindow_release(mWindow);
+}
+
+void ReliableSurface::init() {}
+
+int ReliableSurface::reserveNext() {
+ return OK;
+}
+
+}; // namespace android::uirenderer::renderthread
diff --git a/libs/hwui/platform/host/utils/MessageHandler.h b/libs/hwui/platform/host/utils/MessageHandler.h
new file mode 100644
index 000000000000..51ee48e0c6d2
--- /dev/null
+++ b/libs/hwui/platform/host/utils/MessageHandler.h
@@ -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.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+
+struct Message {
+ Message(int w) {}
+};
+
+class MessageHandler : public virtual android::RefBase {
+protected:
+ virtual ~MessageHandler() override {}
+
+public:
+ /**
+ * Handles a message.
+ */
+ virtual void handleMessage(const Message& message) = 0;
+};
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 22de2f29792d..66e089627a7b 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -35,6 +35,7 @@
#include "Properties.h"
#include "RenderThread.h"
#include "hwui/Canvas.h"
+#include "pipeline/skia/SkiaCpuPipeline.h"
#include "pipeline/skia/SkiaGpuPipeline.h"
#include "pipeline/skia/SkiaOpenGLPipeline.h"
#include "pipeline/skia/SkiaVulkanPipeline.h"
@@ -72,7 +73,7 @@ CanvasContext* ScopedActiveContext::sActiveContext = nullptr;
CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory,
- int32_t uiThreadId, int32_t renderThreadId) {
+ pid_t uiThreadId, pid_t renderThreadId) {
auto renderType = Properties::getRenderPipelineType();
switch (renderType) {
@@ -84,6 +85,12 @@ CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent,
return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread),
uiThreadId, renderThreadId);
+#ifndef __ANDROID__
+ case RenderPipelineType::SkiaCpu:
+ return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
+ std::make_unique<skiapipeline::SkiaCpuPipeline>(thread),
+ uiThreadId, renderThreadId);
+#endif
default:
LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType);
break;
@@ -182,6 +189,7 @@ static void setBufferCount(ANativeWindow* window) {
}
void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) {
+#ifdef __ANDROID__
if (mHardwareBuffer) {
AHardwareBuffer_release(mHardwareBuffer);
mHardwareBuffer = nullptr;
@@ -192,6 +200,7 @@ void CanvasContext::setHardwareBuffer(AHardwareBuffer* buffer) {
mHardwareBuffer = buffer;
}
mRenderPipeline->setHardwareBuffer(mHardwareBuffer);
+#endif
}
void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) {
@@ -561,6 +570,7 @@ Frame CanvasContext::getFrame() {
}
void CanvasContext::draw(bool solelyTextureViewUpdates) {
+#ifdef __ANDROID__
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
if (grContext->isDeviceLost()) {
@@ -571,6 +581,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
return;
}
}
+#endif
SkRect dirty;
mDamageAccumulator.finish(&dirty);
@@ -594,11 +605,13 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (skippedFrameReason) {
mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
+#ifdef __ANDROID__
if (auto grContext = getGrContext()) {
// Submit to ensure that any texture uploads complete and Skia can
// free its staging buffers.
grContext->flushAndSubmit();
}
+#endif
// Notify the callbacks, even if there's nothing to draw so they aren't waiting
// indefinitely
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 1b333bfccbf1..826d00e1f32f 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -140,12 +140,14 @@ void DrawFrameTask::run() {
if (CC_LIKELY(canDrawThisFrame)) {
context->draw(solelyTextureViewUpdates);
} else {
+#ifdef __ANDROID__
// Do a flush in case syncFrameState performed any texture uploads. Since we skipped
// the draw() call, those uploads (or deletes) will end up sitting in the queue.
// Do them now
if (GrDirectContext* grContext = mRenderThread->getGrContext()) {
grContext->flushAndSubmit();
}
+#endif
// wait on fences so tasks don't overlap next frame
context->waitOnFences();
}
@@ -176,11 +178,13 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
bool canDraw = mContext->makeCurrent();
mContext->unpinImages();
+#ifdef __ANDROID__
for (size_t i = 0; i < mLayers.size(); i++) {
if (mLayers[i]) {
mLayers[i]->apply();
}
}
+#endif
mLayers.clear();
mContext->setContentDrawBounds(mContentDrawBounds);
diff --git a/libs/hwui/renderthread/ReliableSurface.h b/libs/hwui/renderthread/ReliableSurface.h
index 595964741049..d6a4d50d3327 100644
--- a/libs/hwui/renderthread/ReliableSurface.h
+++ b/libs/hwui/renderthread/ReliableSurface.h
@@ -21,7 +21,9 @@
#include <apex/window.h>
#include <utils/Errors.h>
#include <utils/Macros.h>
+#ifdef __ANDROID__
#include <utils/NdkUtils.h>
+#endif
#include <utils/StrongPointer.h>
#include <memory>
@@ -62,9 +64,11 @@ private:
mutable std::mutex mMutex;
uint64_t mUsage = AHARDWAREBUFFER_USAGE_GPU_FRAMEBUFFER;
+#ifdef __ANDROID__
AHardwareBuffer_Format mFormat = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
UniqueAHardwareBuffer mScratchBuffer;
ANativeWindowBuffer* mReservedBuffer = nullptr;
+#endif
base::unique_fd mReservedFenceFd;
bool mHasDequeuedBuffer = false;
int mBufferQueueState = OK;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index eab36050896f..715153b5083d 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -42,7 +42,11 @@ namespace renderthread {
RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode,
IContextFactory* contextFactory)
: mRenderThread(RenderThread::getInstance()), mContext(nullptr) {
+#ifdef __ANDROID__
pid_t uiThreadId = pthread_gettid_np(pthread_self());
+#else
+ pid_t uiThreadId = 0;
+#endif
pid_t renderThreadId = getRenderThreadTid();
mContext = mRenderThread.queue().runSync([=, this]() -> CanvasContext* {
CanvasContext* context = CanvasContext::create(mRenderThread, translucent, rootRenderNode,
@@ -90,6 +94,7 @@ void RenderProxy::setName(const char* name) {
}
void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) {
+#ifdef __ANDROID__
if (buffer) {
AHardwareBuffer_acquire(buffer);
}
@@ -99,6 +104,7 @@ void RenderProxy::setHardwareBuffer(AHardwareBuffer* buffer) {
AHardwareBuffer_release(hardwareBuffer);
}
});
+#endif
}
void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
@@ -216,7 +222,9 @@ void RenderProxy::cancelLayerUpdate(DeferredLayerUpdater* layer) {
}
void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) {
+#ifdef __ANDROID__
return mRenderThread.queue().runSync([&]() { layer->detachSurfaceTexture(); });
+#endif
}
void RenderProxy::destroyHardwareResources() {
@@ -324,11 +332,13 @@ void RenderProxy::dumpGraphicsMemory(int fd, bool includeProfileData, bool reset
}
});
}
+#ifdef __ANDROID__
if (!Properties::isolatedProcess) {
std::string grallocInfo;
GraphicBufferAllocator::getInstance().dump(grallocInfo);
dprintf(fd, "%s\n", grallocInfo.c_str());
}
+#endif
}
void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -352,7 +362,11 @@ void RenderProxy::rotateProcessStatsBuffer() {
}
int RenderProxy::getRenderThreadTid() {
+#ifdef __ANDROID__
return mRenderThread.getTid();
+#else
+ return 0;
+#endif
}
void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
@@ -461,7 +475,7 @@ void RenderProxy::prepareToDraw(Bitmap& bitmap) {
int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
ATRACE_NAME("HardwareBitmap readback");
RenderThread& thread = RenderThread::getInstance();
- if (gettid() == thread.getTid()) {
+ if (RenderThread::isCurrent()) {
// TODO: fix everything that hits this. We should never be triggering a readback ourselves.
return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap);
} else {
@@ -472,7 +486,7 @@ int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) {
int RenderProxy::copyImageInto(const sk_sp<SkImage>& image, SkBitmap* bitmap) {
RenderThread& thread = RenderThread::getInstance();
- if (gettid() == thread.getTid()) {
+ if (RenderThread::isCurrent()) {
// TODO: fix everything that hits this. We should never be triggering a readback ourselves.
return (int)thread.readback().copyImageInto(image, bitmap);
} else {
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 207ccbee0b50..871e9ab87299 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -80,4 +80,7 @@ interface ISessionManager {
boolean hasCustomMediaSessionPolicyProvider(String componentName);
int getSessionPolicies(in MediaSession.Token token);
void setSessionPolicies(in MediaSession.Token token, int policies);
+
+ // For testing of temporarily engaged sessions.
+ void expireTempEngagedSessions();
}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 0ee9d595d875..85ad160f6d66 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.3.1"
+agp = "8.3.2"
compose-compiler = "1.5.11"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip
index 5c9634782bbe..7a9ac5afe013 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.6-bin.zip
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-8.7-bin.zip
Binary files differ
diff --git a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
index 50ff9dff549b..182095e76e76 100644
--- a/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/SettingsLib/Spa/gradle/wrapper/gradle-wrapper.properties
@@ -16,6 +16,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=gradle-8.6-bin.zip
+distributionUrl=gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 2f2ac2467a6c..6344501ce789 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -65,7 +65,7 @@ dependencies {
api("androidx.lifecycle:lifecycle-runtime-compose")
api("androidx.navigation:navigation-compose:2.8.0-alpha05")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
- api("com.google.android.material:material:1.7.0-alpha03")
+ api("com.google.android.material:material:1.11.0")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
implementation("com.airbnb.android:lottie-compose:5.2.0")
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7e6b004be9b8..4640de304ed8 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1148,6 +1148,16 @@
<!-- [CHAR_LIMIT=80] Label for battery charging future pause -->
<string name="power_charging_future_paused"><xliff:g id="level">%1$s</xliff:g> - Charging</string>
+ <!-- [CHAR_LIMIT=40] Label for battery level when fast charging with duration. -->
+ <string name="power_fast_charging_duration_v2"><xliff:g id="level">%1$s</xliff:g> - <xliff:g id="status">%2$s</xliff:g> - Full by <xliff:g id="time">%3$s</xliff:g></string>
+ <!-- [CHAR_LIMIT=40] Label for battery level when non-fast charging with duration. -->
+ <string name="power_charging_duration_v2"><xliff:g id="level">%1$s</xliff:g> - Fully charged by <xliff:g id="time">%2$s</xliff:g></string>
+
+ <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging. -->
+ <string name="power_remaining_charging_duration_only_v2">Fully charged by <xliff:g id="time">%1$s</xliff:g></string>
+ <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging. -->
+ <string name="power_remaining_fast_charging_duration_only_v2">Full by <xliff:g id="time">%1$s</xliff:g></string>
+
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
<!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging from an unknown source. -->
@@ -1171,6 +1181,11 @@
<!-- [CHAR_LIMIT=None] Battery Info screen. Value for a status item. A state which device charging on hold -->
<string name="battery_info_status_charging_on_hold">Charging on hold</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging isn't fast. -->
+ <string name="battery_info_status_charging_v2">Charging</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging speed is fast. -->
+ <string name="battery_info_status_charging_fast_v2">Fast charging</string>
+
<!-- Summary for settings preference disabled by administrator [CHAR LIMIT=50] -->
<string name="disabled_by_admin_summary_text">Controlled by admin</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index e95a506376fd..563f02d95f3c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -65,6 +65,7 @@ import com.android.launcher3.icons.IconFactory;
import com.android.launcher3.util.UserIconInfo;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.settingslib.fuelgauge.BatteryUtils;
import com.android.settingslib.utils.BuildCompatUtils;
import java.util.List;
@@ -246,25 +247,23 @@ public class Utils {
} else {
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
if (compactStatus) {
- statusString = res.getString(R.string.battery_info_status_charging);
+ statusString = getRegularChargingStatusString(res);
} else if (batteryStatus.isPluggedInWired()) {
switch (batteryStatus.getChargingSpeed(context)) {
case BatteryStatus.CHARGING_FAST:
- statusString =
- res.getString(R.string.battery_info_status_charging_fast);
+ statusString = getFastChargingStatusString(res);
break;
case BatteryStatus.CHARGING_SLOWLY:
- statusString =
- res.getString(R.string.battery_info_status_charging_slow);
+ statusString = getSlowChargingStatusString(res);
break;
default:
- statusString = res.getString(R.string.battery_info_status_charging);
+ statusString = getRegularChargingStatusString(res);
break;
}
} else if (batteryStatus.isPluggedInDock()) {
- statusString = res.getString(R.string.battery_info_status_charging_dock);
+ statusString = getDockChargingStatusString(res);
} else {
- statusString = res.getString(R.string.battery_info_status_charging_wireless);
+ statusString = getWirelessChargingStatusString(res);
}
} else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
statusString = res.getString(R.string.battery_info_status_discharging);
@@ -276,6 +275,41 @@ public class Utils {
return statusString;
}
+ private static String getFastChargingStatusString(Resources res) {
+ return res.getString(
+ BatteryUtils.isChargingStringV2Enabled()
+ ? R.string.battery_info_status_charging_fast_v2
+ : R.string.battery_info_status_charging_fast);
+ }
+
+ private static String getSlowChargingStatusString(Resources res) {
+ return res.getString(
+ BatteryUtils.isChargingStringV2Enabled()
+ ? R.string.battery_info_status_charging_v2
+ : R.string.battery_info_status_charging_slow);
+ }
+
+ private static String getRegularChargingStatusString(Resources res) {
+ return res.getString(
+ BatteryUtils.isChargingStringV2Enabled()
+ ? R.string.battery_info_status_charging_v2
+ : R.string.battery_info_status_charging);
+ }
+
+ private static String getWirelessChargingStatusString(Resources res) {
+ return res.getString(
+ BatteryUtils.isChargingStringV2Enabled()
+ ? R.string.battery_info_status_charging_v2
+ : R.string.battery_info_status_charging_wireless);
+ }
+
+ private static String getDockChargingStatusString(Resources res) {
+ return res.getString(
+ BatteryUtils.isChargingStringV2Enabled()
+ ? R.string.battery_info_status_charging_v2
+ : R.string.battery_info_status_charging_dock);
+ }
+
public static ColorStateList getColorAccent(Context context) {
return getColorAttr(context, android.R.attr.colorAccent);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 0996d52b0e30..e926b1684348 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -57,6 +57,7 @@ public class BluetoothEventManager {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final LocalBluetoothAdapter mLocalAdapter;
+ private final LocalBluetoothManager mBtManager;
private final CachedBluetoothDeviceManager mDeviceManager;
private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter;
private final Map<String, Handler> mHandlerMap;
@@ -80,10 +81,15 @@ public class BluetoothEventManager {
* userHandle passed in is {@code null}, we register event receiver for the
* {@code context.getUser()} handle.
*/
- BluetoothEventManager(LocalBluetoothAdapter adapter,
- CachedBluetoothDeviceManager deviceManager, Context context,
- android.os.Handler handler, @Nullable UserHandle userHandle) {
+ BluetoothEventManager(
+ LocalBluetoothAdapter adapter,
+ LocalBluetoothManager btManager,
+ CachedBluetoothDeviceManager deviceManager,
+ Context context,
+ android.os.Handler handler,
+ @Nullable UserHandle userHandle) {
mLocalAdapter = adapter;
+ mBtManager = btManager;
mDeviceManager = deviceManager;
mAdapterIntentFilter = new IntentFilter();
mProfileIntentFilter = new IntentFilter();
@@ -210,11 +216,27 @@ public class BluetoothEventManager {
}
}
- void dispatchProfileConnectionStateChanged(@NonNull CachedBluetoothDevice device, int state,
- int bluetoothProfile) {
+ void dispatchProfileConnectionStateChanged(
+ @NonNull CachedBluetoothDevice device, int state, int bluetoothProfile) {
for (BluetoothCallback callback : mCallbacks) {
callback.onProfileConnectionStateChanged(device, state, bluetoothProfile);
}
+
+ // Trigger updateFallbackActiveDeviceIfNeeded when ASSISTANT profile disconnected when
+ // audio sharing is enabled.
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
+ && state == BluetoothAdapter.STATE_DISCONNECTED
+ && BluetoothUtils.isAudioSharingEnabled()) {
+ LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ if (profileManager != null
+ && profileManager.getLeAudioBroadcastProfile() != null
+ && profileManager.getLeAudioBroadcastProfile().isProfileReady()
+ && profileManager.getLeAudioBroadcastAssistantProfile() != null
+ && profileManager.getLeAudioBroadcastAssistantProfile().isProfileReady()) {
+ Log.d(TAG, "updateFallbackActiveDeviceIfNeeded, ASSISTANT profile disconnected");
+ profileManager.getLeAudioBroadcastProfile().updateFallbackActiveDeviceIfNeeded();
+ }
+ }
}
private void dispatchConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) {
@@ -536,7 +558,6 @@ public class BluetoothEventManager {
default:
Log.w(TAG, "ActiveDeviceChangedHandler: unknown action " + action);
return;
-
}
dispatchAclStateChanged(activeDevice, state);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
index 53c6075ccff4..c4300d214c0c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java
@@ -21,11 +21,11 @@ import android.os.Handler;
import android.os.UserHandle;
import android.util.Log;
-import java.lang.ref.WeakReference;
-
import androidx.annotation.Nullable;
import androidx.annotation.RequiresPermission;
+import java.lang.ref.WeakReference;
+
/**
* LocalBluetoothManager provides a simplified interface on top of a subset of
* the Bluetooth API. Note that {@link #getInstance} will return null
@@ -111,10 +111,17 @@ public class LocalBluetoothManager {
mContext = context.getApplicationContext();
mLocalAdapter = adapter;
mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, this);
- mEventManager = new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mContext,
- handler, userHandle);
- mProfileManager = new LocalBluetoothProfileManager(mContext,
- mLocalAdapter, mCachedDeviceManager, mEventManager);
+ mEventManager =
+ new BluetoothEventManager(
+ mLocalAdapter,
+ this,
+ mCachedDeviceManager,
+ mContext,
+ handler,
+ userHandle);
+ mProfileManager =
+ new LocalBluetoothProfileManager(
+ mContext, mLocalAdapter, mCachedDeviceManager, mEventManager);
mProfileManager.updateLocalProfiles();
mEventManager.readPairedDevices();
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index 92db50878a70..327e470e7d22 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -21,11 +21,14 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.provider.Settings;
+import android.os.SystemProperties;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.view.accessibility.AccessibilityManager;
+import androidx.annotation.VisibleForTesting;
+
import java.util.List;
public final class BatteryUtils {
@@ -33,6 +36,9 @@ public final class BatteryUtils {
/** The key to get the time to full from Settings.Global */
public static final String GLOBAL_TIME_TO_FULL_MILLIS = "time_to_full_millis";
+ /** The system property key to check whether the charging string v2 is enabled or not. */
+ public static final String PROPERTY_CHARGING_STRING_V2_KEY = "charging_string.apply_v2";
+
/** Gets the latest sticky battery intent from the Android system. */
public static Intent getBatteryIntent(Context context) {
return context.registerReceiver(
@@ -75,4 +81,25 @@ public final class BatteryUtils {
final UserManager userManager = context.getSystemService(UserManager.class);
return userManager.isManagedProfile() && !userManager.isSystemUser();
}
+
+ private static Boolean sChargingStringV2Enabled = null;
+
+ /** Returns {@code true} if the charging string v2 is enabled. */
+ public static boolean isChargingStringV2Enabled() {
+ if (sChargingStringV2Enabled == null) {
+ sChargingStringV2Enabled =
+ SystemProperties.getBoolean(PROPERTY_CHARGING_STRING_V2_KEY, false);
+ }
+ return sChargingStringV2Enabled;
+ }
+
+
+ /** Used to override the system property to enable or reset for charging string V2. */
+ @VisibleForTesting
+ public static void setChargingStringV2Enabled(Boolean enabled) {
+ SystemProperties.set(
+ BatteryUtils.PROPERTY_CHARGING_STRING_V2_KEY,
+ enabled == null ? "" : String.valueOf(enabled));
+ BatteryUtils.sChargingStringV2Enabled = enabled;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
index 22726549ce05..5ed59996bee3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/PowerUtil.java
@@ -33,7 +33,7 @@ import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
-/** Utility class for keeping power related strings consistent**/
+/** Utility class for keeping power related strings consistent. **/
public class PowerUtil {
private static final long SEVEN_MINUTES_MILLIS = TimeUnit.MINUTES.toMillis(7);
@@ -221,4 +221,19 @@ public class PowerUtil {
return time - remainder + multiple;
}
}
+
+ /** Gets the rounded target time string in a short format. */
+ public static String getTargetTimeShortString(
+ Context context, long targetTimeOffsetMs, long currentTimeMs) {
+ final long roundedTimeOfDayMs =
+ roundTimeToNearestThreshold(
+ currentTimeMs + targetTimeOffsetMs, FIFTEEN_MINUTES_MILLIS);
+
+ // convert the time to a properly formatted string.
+ String skeleton = android.text.format.DateFormat.getTimeFormatString(context);
+ DateFormat fmt = DateFormat.getInstanceForSkeleton(skeleton);
+ Date date = Date.from(Instant.ofEpochMilli(roundedTimeOfDayMs));
+ return fmt.format(date);
+ }
}
+
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java
index 50f5b9d81000..69f6305fa1b2 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/bluetooth/BluetoothEventManagerIntegTest.java
@@ -20,6 +20,8 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -37,13 +39,11 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import static java.util.concurrent.TimeUnit.SECONDS;
-
import java.util.concurrent.CountDownLatch;
/**
- * Test that verifies that BluetoothEventManager can receive broadcasts for non-current
- * users for all bluetooth events.
+ * Test that verifies that BluetoothEventManager can receive broadcasts for non-current users for
+ * all bluetooth events.
*
* <p>Creation and deletion of users takes a long time, so marking this as a LargeTest.
*/
@@ -64,9 +64,14 @@ public class BluetoothEventManagerIntegTest {
mContext = InstrumentationRegistry.getTargetContext();
mUserManager = UserManager.get(mContext);
- mBluetoothEventManager = new BluetoothEventManager(
- mock(LocalBluetoothAdapter.class), mock(CachedBluetoothDeviceManager.class),
- mContext, /* handler= */ null, UserHandle.ALL);
+ mBluetoothEventManager =
+ new BluetoothEventManager(
+ mock(LocalBluetoothAdapter.class),
+ mock(LocalBluetoothManager.class),
+ mock(CachedBluetoothDeviceManager.class),
+ mContext,
+ /* handler= */ null,
+ UserHandle.ALL);
// Create and start another user in the background.
mOtherUser = mUserManager.createUser("TestUser", /* flags= */ 0);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 48bbf4ea6a65..b1489be943e6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.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 static org.mockito.Mockito.when;
@@ -29,35 +30,47 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.telephony.TelephonyManager;
import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
public class BluetoothEventManagerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final String DEVICE_NAME = "test_device_name";
@Mock
private LocalBluetoothAdapter mLocalAdapter;
@Mock
+ private LocalBluetoothManager mBtManager;
+ @Mock
private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock
private BluetoothCallback mBluetoothCallback;
@@ -96,8 +109,15 @@ public class BluetoothEventManagerTest {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
- mBluetoothEventManager = new BluetoothEventManager(mLocalAdapter,
- mCachedDeviceManager, mContext, /* handler= */ null, /* userHandle= */ null);
+ mBluetoothEventManager =
+ new BluetoothEventManager(
+ mLocalAdapter,
+ mBtManager,
+ mCachedDeviceManager,
+ mContext,
+ /* handler= */ null,
+ /* userHandle= */ null);
+ when(mBtManager.getProfileManager()).thenReturn(mLocalProfileManager);
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
when(mHfpProfile.isProfileReady()).thenReturn(true);
when(mA2dpProfile.isProfileReady()).thenReturn(true);
@@ -113,8 +133,13 @@ public class BluetoothEventManagerTest {
public void ifUserHandleIsNull_registerReceiverIsCalled() {
Context mockContext = mock(Context.class);
BluetoothEventManager eventManager =
- new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mockContext,
- /* handler= */ null, /* userHandle= */ null);
+ new BluetoothEventManager(
+ mLocalAdapter,
+ mBtManager,
+ mCachedDeviceManager,
+ mockContext,
+ /* handler= */ null,
+ /* userHandle= */ null);
verify(mockContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
@@ -124,8 +149,13 @@ public class BluetoothEventManagerTest {
public void ifUserHandleSpecified_registerReceiverAsUserIsCalled() {
Context mockContext = mock(Context.class);
BluetoothEventManager eventManager =
- new BluetoothEventManager(mLocalAdapter, mCachedDeviceManager, mockContext,
- /* handler= */ null, UserHandle.ALL);
+ new BluetoothEventManager(
+ mLocalAdapter,
+ mBtManager,
+ mCachedDeviceManager,
+ mockContext,
+ /* handler= */ null,
+ UserHandle.ALL);
verify(mockContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq(UserHandle.ALL),
any(IntentFilter.class), eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
@@ -172,6 +202,160 @@ public class BluetoothEventManagerTest {
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP);
}
+ /**
+ * dispatchProfileConnectionStateChanged should not call {@link
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when audio sharing flag is off.
+ */
+ @Test
+ public void dispatchProfileConnectionStateChanged_flagOff_noUpdateFallbackDevice() {
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
+ when(broadcast.isProfileReady()).thenReturn(true);
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mock(LocalBluetoothLeBroadcastAssistant.class);
+ when(assistant.isProfileReady()).thenReturn(true);
+ LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
+ when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ }
+
+ /**
+ * dispatchProfileConnectionStateChanged should not call {@link
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when the device does not
+ * support audio sharing.
+ */
+ @Test
+ public void dispatchProfileConnectionStateChanged_notSupport_noUpdateFallbackDevice() {
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
+ when(broadcast.isProfileReady()).thenReturn(true);
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mock(LocalBluetoothLeBroadcastAssistant.class);
+ when(assistant.isProfileReady()).thenReturn(true);
+ LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
+ when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ }
+
+ /**
+ * dispatchProfileConnectionStateChanged should not call {@link
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when audio sharing profile is
+ * not ready.
+ */
+ @Test
+ public void dispatchProfileConnectionStateChanged_profileNotReady_noUpdateFallbackDevice() {
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
+ when(broadcast.isProfileReady()).thenReturn(false);
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mock(LocalBluetoothLeBroadcastAssistant.class);
+ when(assistant.isProfileReady()).thenReturn(true);
+ LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
+ when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ }
+
+ /**
+ * dispatchProfileConnectionStateChanged should not call {@link
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when triggered for profile
+ * other than LE_AUDIO_BROADCAST_ASSISTANT or state other than STATE_DISCONNECTED.
+ */
+ @Test
+ public void dispatchProfileConnectionStateChanged_notAssistantProfile_noUpdateFallbackDevice() {
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
+ when(broadcast.isProfileReady()).thenReturn(true);
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mock(LocalBluetoothLeBroadcastAssistant.class);
+ when(assistant.isProfileReady()).thenReturn(true);
+ LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
+ when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO);
+
+ verify(broadcast, times(0)).updateFallbackActiveDeviceIfNeeded();
+ }
+
+ /**
+ * dispatchProfileConnectionStateChanged should call {@link
+ * LocalBluetoothLeBroadcast}#updateFallbackActiveDeviceIfNeeded when assistant profile is
+ * disconnected and audio sharing is enabled.
+ */
+ @Test
+ public void dispatchProfileConnectionStateChanged_audioSharing_updateFallbackDevice() {
+ ShadowBluetoothAdapter shadowBluetoothAdapter =
+ Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+ shadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ shadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+ BluetoothStatusCodes.FEATURE_SUPPORTED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+ LocalBluetoothLeBroadcast broadcast = mock(LocalBluetoothLeBroadcast.class);
+ when(broadcast.isProfileReady()).thenReturn(true);
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mock(LocalBluetoothLeBroadcastAssistant.class);
+ when(assistant.isProfileReady()).thenReturn(true);
+ LocalBluetoothProfileManager profileManager = mock(LocalBluetoothProfileManager.class);
+ when(profileManager.getLeAudioBroadcastProfile()).thenReturn(broadcast);
+ when(profileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(assistant);
+ when(mBtManager.getProfileManager()).thenReturn(profileManager);
+ mBluetoothEventManager.dispatchProfileConnectionStateChanged(
+ mCachedBluetoothDevice,
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+
+ verify(broadcast).updateFallbackActiveDeviceIfNeeded();
+ }
+
@Test
public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
index 4f8fa2fdb96e..cef083584744 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java
@@ -56,7 +56,8 @@ import java.util.List;
@Config(shadows = {ShadowBluetoothAdapter.class})
public class LocalBluetoothProfileManagerTest {
private final static long HISYNCID = 10;
-
+ @Mock
+ private LocalBluetoothManager mBtManager;
@Mock
private CachedBluetoothDeviceManager mDeviceManager;
@Mock
@@ -77,13 +78,21 @@ public class LocalBluetoothProfileManagerTest {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
mLocalBluetoothAdapter = LocalBluetoothAdapter.getInstance();
- mEventManager = spy(new BluetoothEventManager(mLocalBluetoothAdapter, mDeviceManager,
- mContext, /* handler= */ null, /* userHandle= */ null));
+ mEventManager =
+ spy(
+ new BluetoothEventManager(
+ mLocalBluetoothAdapter,
+ mBtManager,
+ mDeviceManager,
+ mContext,
+ /* handler= */ null,
+ /* userHandle= */ null));
mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
when(mDeviceManager.findDevice(mDevice)).thenReturn(mCachedBluetoothDevice);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice);
- mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter,
- mDeviceManager, mEventManager);
+ mProfileManager =
+ new LocalBluetoothProfileManager(
+ mContext, mLocalBluetoothAdapter, mDeviceManager, mEventManager);
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index 2e7905f2e1e4..cbc382b6b920 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -20,30 +20,24 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
+import android.app.AlarmManager;
import android.content.Context;
+import androidx.test.core.app.ApplicationProvider;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.time.Duration;
+import java.time.Instant;
+import java.util.Locale;
import java.util.regex.Pattern;
@RunWith(RobolectricTestRunner.class)
public class PowerUtilTest {
- private static final String TEST_BATTERY_LEVEL_10 = "10%";
- private static final long TEN_SEC_MILLIS = Duration.ofSeconds(10).toMillis();
- private static final long SEVENTEEN_MIN_MILLIS = Duration.ofMinutes(17).toMillis();
- private static final long FIVE_MINUTES_MILLIS = Duration.ofMinutes(5).toMillis();
- private static final long TEN_MINUTES_MILLIS = Duration.ofMinutes(10).toMillis();
- private static final long THREE_DAYS_MILLIS = Duration.ofDays(3).toMillis();
- private static final long TEN_HOURS_MILLIS = Duration.ofHours(10).toMillis();
- private static final long THIRTY_HOURS_MILLIS = Duration.ofHours(30).toMillis();
- private static final String NORMAL_CASE_EXPECTED_PREFIX = "Should last until about";
- private static final String ENHANCED_SUFFIX = " based on your usage";
private static final String BATTERY_RUN_OUT_PREFIX = "Battery may run out by";
// matches a time (ex: '1:15 PM', '2 AM', '23:00')
private static final String TIME_OF_DAY_REGEX = " (\\d)+:?(\\d)* ((AM)*)|((PM)*)";
@@ -55,29 +49,31 @@ public class PowerUtilTest {
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- mContext = spy(RuntimeEnvironment.application);
+ mContext = spy(ApplicationProvider.getApplicationContext());
}
@Test
public void getBatteryTipStringFormatted_moreThanOneDay_usesCorrectString() {
- String info = PowerUtil.getBatteryTipStringFormatted(mContext,
- THREE_DAYS_MILLIS);
+ var threeDayMillis = Duration.ofDays(3).toMillis();
+
+ String batteryTipString = PowerUtil.getBatteryTipStringFormatted(mContext, threeDayMillis);
- assertThat(info).isEqualTo("More than 3 days left");
+ assertThat(batteryTipString).isEqualTo("More than 3 days left");
}
@Test
public void getBatteryTipStringFormatted_lessThanOneDay_usesCorrectString() {
- String info = PowerUtil.getBatteryTipStringFormatted(mContext,
- SEVENTEEN_MIN_MILLIS);
+ var drainTimeMs = Duration.ofMinutes(17).toMillis();
+
+ String batteryTipString = PowerUtil.getBatteryTipStringFormatted(mContext, drainTimeMs);
// ex: Battery may run out by 1:15 PM
- assertThat(info).containsMatch(Pattern.compile(
- BATTERY_RUN_OUT_PREFIX + TIME_OF_DAY_REGEX));
+ assertThat(batteryTipString)
+ .containsMatch(Pattern.compile(BATTERY_RUN_OUT_PREFIX + TIME_OF_DAY_REGEX));
}
@Test
- public void testRoundToNearestThreshold_roundsCorrectly() {
+ public void roundTimeToNearestThreshold_roundsCorrectly() {
// test some pretty normal values
assertThat(PowerUtil.roundTimeToNearestThreshold(1200, 1000)).isEqualTo(1000);
assertThat(PowerUtil.roundTimeToNearestThreshold(800, 1000)).isEqualTo(1000);
@@ -89,4 +85,17 @@ public class PowerUtilTest {
assertThat(PowerUtil.roundTimeToNearestThreshold(-120, 100)).isEqualTo(100);
assertThat(PowerUtil.roundTimeToNearestThreshold(-200, -75)).isEqualTo(225);
}
+
+ @Test
+ public void getTargetTimeShortString_returnsTimeShortString() {
+ mContext.getSystemService(AlarmManager.class).setTimeZone("UTC");
+ mContext.getResources().getConfiguration().setLocale(Locale.US);
+ var currentTimeMs = Instant.parse("2024-06-06T15:00:00Z").toEpochMilli();
+ var remainingTimeMs = Duration.ofMinutes(30).toMillis();
+
+ var actualTimeString =
+ PowerUtil.getTargetTimeShortString(mContext, remainingTimeMs, currentTimeMs);
+
+ assertThat(actualTimeString).isEqualTo("3:30 PM");
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
index c7e96bcdb856..00e47729a796 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java
@@ -38,6 +38,8 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto
private List<BluetoothDevice> mMostRecentlyConnectedDevices;
private BluetoothProfile.ServiceListener mServiceListener;
private ParcelUuid[] mParcelUuids;
+ private int mIsLeAudioBroadcastSourceSupported;
+ private int mIsLeAudioBroadcastAssistantSupported;
@Implementation
protected boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
@@ -97,4 +99,22 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto
public void setUuids(ParcelUuid[] uuids) {
mParcelUuids = uuids;
}
+
+ @Implementation
+ protected int isLeAudioBroadcastSourceSupported() {
+ return mIsLeAudioBroadcastSourceSupported;
+ }
+
+ public void setIsLeAudioBroadcastSourceSupported(int isSupported) {
+ mIsLeAudioBroadcastSourceSupported = isSupported;
+ }
+
+ @Implementation
+ protected int isLeAudioBroadcastAssistantSupported() {
+ return mIsLeAudioBroadcastAssistantSupported;
+ }
+
+ public void setIsLeAudioBroadcastAssistantSupported(int isSupported) {
+ mIsLeAudioBroadcastAssistantSupported = isSupported;
+ }
}
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 e20425d4b98c..94f884673fbd 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteAnimationRunnerCompat.java
@@ -36,6 +36,7 @@ import android.view.WindowManager;
import android.view.WindowManager.TransitionOldType;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransitionStub;
import android.window.TransitionInfo;
import com.android.wm.shell.shared.CounterRotator;
@@ -69,8 +70,8 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner
}
/** Wraps a remote animation runner in a remote-transition. */
- public static IRemoteTransition.Stub wrap(IRemoteAnimationRunner runner) {
- return new IRemoteTransition.Stub() {
+ public static RemoteTransitionStub wrap(IRemoteAnimationRunner runner) {
+ return new RemoteTransitionStub() {
final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
@Override
@@ -233,11 +234,6 @@ public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner
runner.onAnimationCancelled();
finishRunnable.run();
}
-
- @Override
- public void onTransitionConsumed(IBinder iBinder, boolean aborted)
- throws RemoteException {
- }
};
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 3ec5508c81b3..d59f1f5bbe25 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -22,11 +22,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.bouncer.ui.BouncerDialogFactory
@@ -35,9 +32,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
object Bouncer {
object Elements {
@@ -57,13 +52,7 @@ constructor(
override val key = Scenes.Bouncer
override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
- MutableStateFlow(
- mapOf(
- Back to UserActionResult(Scenes.Lockscreen),
- Swipe(SwipeDirection.Down) to UserActionResult(Scenes.Lockscreen),
- )
- )
- .asStateFlow()
+ viewModel.destinationScenes
@Composable
override fun SceneScope.Content(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
index 2af042aac5b4..e1ee01e78566 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt
@@ -28,11 +28,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.slice.Slice
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel
import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
/** ANC popup up displaying ANC control [Slice]. */
@@ -41,10 +43,12 @@ class AncPopup
constructor(
private val volumePanelPopup: VolumePanelPopup,
private val viewModel: AncViewModel,
+ private val uiEventLogger: UiEventLogger,
) {
/** Shows a popup with the [expandable] animation. */
fun show(expandable: Expandable?) {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN)
volumePanelPopup.show(expandable, { Title() }, { Content(it) })
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
index eed54dab6faf..9a98bdeec8f1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -26,6 +26,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.toColor
@@ -34,6 +35,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar
import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
class SpatialAudioPopup
@@ -41,10 +43,17 @@ class SpatialAudioPopup
constructor(
private val viewModel: SpatialAudioViewModel,
private val volumePanelPopup: VolumePanelPopup,
+ private val uiEventLogger: UiEventLogger,
) {
/** Shows a popup with the [expandable] animation. */
fun show(expandable: Expandable) {
+ uiEventLogger.logWithPosition(
+ VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN,
+ 0,
+ null,
+ viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isChecked }
+ )
volumePanelPopup.show(expandable, { Title() }, { Content(it) })
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index f89669c8456c..a54d005c990a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -85,6 +85,7 @@ fun ColumnVolumeSliders(
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
@@ -131,6 +132,7 @@ fun ColumnVolumeSliders(
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index b284c691ef0e..bb17499f021f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -46,6 +46,7 @@ fun GridVolumeSliders(
onValueChange = { newValue: Float ->
sliderViewModel.onValueChanged(sliderState, newValue)
},
+ onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
sliderColors = sliderColors,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index 28cd37ea9327..228d29259038 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -51,6 +51,7 @@ import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.Sl
fun VolumeSlider(
state: SliderState,
onValueChange: (newValue: Float) -> Unit,
+ onValueChangeFinished: (() -> Unit)? = null,
onIconTapped: () -> Unit,
modifier: Modifier = Modifier,
sliderColors: PlatformSliderColors,
@@ -85,6 +86,7 @@ fun VolumeSlider(
value = value,
valueRange = state.valueRange,
onValueChange = onValueChange,
+ onValueChangeFinished = onValueChangeFinished,
enabled = state.isEnabled,
icon = {
state.icon?.let {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 3afca96e07a0..0db0e0767767 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -18,6 +18,10 @@ package com.android.systemui.bouncer.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -34,7 +38,10 @@ import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
+import com.android.systemui.truth.containsEntriesExactly
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -193,6 +200,23 @@ class BouncerViewModelTest : SysuiTestCase() {
assertThat(isFoldSplitRequired).isTrue()
}
+ @Test
+ fun destinationScenes() =
+ testScope.runTest {
+ val destinationScenes by collectLastValue(underTest.destinationScenes)
+ kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
+ runCurrent()
+
+ kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
+ runCurrent()
+
+ assertThat(destinationScenes)
+ .containsEntriesExactly(
+ Back to UserActionResult(Scenes.QuickSettings),
+ Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
+ )
+ }
+
private fun authMethodsToTest(): List<AuthenticationMethodModel> {
return listOf(None, Pin, Password, Pattern, Sim)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
index 70582daf8e9f..c88e432d15d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -191,7 +191,7 @@ class KeyguardClockInteractorTest : SysuiTestCase() {
val value by collectLastValue(underTest.clockShouldBeCentered)
kosmos.shadeRepository.setShadeMode(ShadeMode.Split)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
- kosmos.headsUpNotificationRepository.headsUpAnimatingAway.value = true
+ kosmos.headsUpNotificationRepository.isHeadsUpAnimatingAway.value = true
kosmos.keyguardRepository.setIsDozing(true)
assertThat(value).isEqualTo(false)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 15c9cf73d51d..412292554e73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -55,28 +55,29 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
val testScope = kosmos.testScope
@Test
- fun transitionCollectorsReceivesOnlyAppropriateEvents() = runTest {
- val lockscreenToAodSteps by collectValues(underTest.lockscreenToAodTransition)
- val aodToLockscreenSteps by collectValues(underTest.aodToLockscreenTransition)
-
- val steps = mutableListOf<TransitionStep>()
- steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
- steps.add(TransitionStep(AOD, GONE, 1f, FINISHED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
+ fun transitionCollectorsReceivesOnlyAppropriateEvents() =
+ testScope.runTest {
+ val lockscreenToAodSteps by collectValues(underTest.transition(LOCKSCREEN, AOD))
+ val aodToLockscreenSteps by collectValues(underTest.transition(AOD, LOCKSCREEN))
- steps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
+ val steps = mutableListOf<TransitionStep>()
+ steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
+ steps.add(TransitionStep(AOD, GONE, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
- assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
- assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
- }
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
+ assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
+ }
@Test
fun dozeAmountTransitionTest_AodToFromLockscreen() =
@@ -187,59 +188,60 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
}
@Test
- fun finishedKeyguardTransitionStepTests() = runTest {
- val finishedSteps by collectValues(underTest.finishedKeyguardTransitionStep)
+ fun finishedKeyguardTransitionStepTests() =
+ testScope.runTest {
+ val finishedSteps by collectValues(underTest.finishedKeyguardTransitionStep)
+ val steps = mutableListOf<TransitionStep>()
- val steps = mutableListOf<TransitionStep>()
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
- steps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
+ // Ignore the default state.
+ assertThat(finishedSteps.subList(1, finishedSteps.size))
+ .isEqualTo(listOf(steps[2], steps[5]))
}
- // Ignore the default state.
- assertThat(finishedSteps.subList(1, finishedSteps.size))
- .isEqualTo(listOf(steps[2], steps[5]))
- }
-
@Test
- fun startedKeyguardTransitionStepTests() = runTest {
- val startedSteps by collectValues(underTest.startedKeyguardTransitionStep)
+ fun startedKeyguardTransitionStepTests() =
+ testScope.runTest {
+ val startedSteps by collectValues(underTest.startedKeyguardTransitionStep)
- val steps = mutableListOf<TransitionStep>()
+ val steps = mutableListOf<TransitionStep>()
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
- steps.forEach {
- repository.sendTransitionStep(it)
- runCurrent()
- }
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
- assertThat(startedSteps)
- .isEqualTo(
- listOf(
- // The initial transition will also get sent when collect started
- TransitionStep(OFF, LOCKSCREEN, 0f, STARTED),
- steps[0],
- steps[3],
- steps[6]
+ assertThat(startedSteps)
+ .isEqualTo(
+ listOf(
+ // The initial transition will also get sent when collect started
+ TransitionStep(OFF, LOCKSCREEN, 0f, STARTED),
+ steps[0],
+ steps[3],
+ steps[6]
+ )
)
- )
- }
+ }
@Test
fun transitionValue() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
index f0607f4b70e1..0ac7ff5232a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.SysuiTestCase
@@ -35,10 +36,9 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardTransitionAnimationFlowTest : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index dddf6485d0f4..4c16a339d696 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -49,9 +49,7 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
val configurationRepository = kosmos.fakeConfigurationRepository
- val underTest by lazy {
- kosmos.occludedToLockscreenTransitionViewModel
- }
+ val underTest by lazy { kosmos.occludedToLockscreenTransitionViewModel }
@Test
fun lockscreenFadeIn() =
@@ -164,25 +162,6 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() {
values.forEach { assertThat(it).isEqualTo(1f) }
}
- @Test
- fun deviceEntryBackgroundView_noUdfpsEnrolled_noUpdates() =
- testScope.runTest {
- fingerprintPropertyRepository.supportsRearFps()
- biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
- val values by collectValues(underTest.deviceEntryBackgroundViewAlpha)
-
- keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
- keyguardTransitionRepository.sendTransitionStep(step(0.1f))
- keyguardTransitionRepository.sendTransitionStep(step(0.3f))
- keyguardTransitionRepository.sendTransitionStep(step(0.4f))
- keyguardTransitionRepository.sendTransitionStep(step(0.5f))
- keyguardTransitionRepository.sendTransitionStep(step(0.6f))
- keyguardTransitionRepository.sendTransitionStep(step(0.8f))
- keyguardTransitionRepository.sendTransitionStep(step(1f))
-
- assertThat(values).isEmpty() // no updates
- }
-
private fun step(
value: Float,
state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 956ef661d467..33eb90acdcb3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -26,7 +26,9 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -144,6 +146,37 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
assertThat(smartspaceMediaData?.isActive).isFalse()
}
+ @Test
+ fun addMediaDataLoadingState() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(underTest.mediaDataLoadedStates)
+ val instanceId = InstanceId.fakeInstanceId(123)
+ val mediaLoadedStates = mutableListOf(MediaDataLoadingModel.Loaded(instanceId))
+
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(instanceId))
+
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Removed(instanceId))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+ }
+
+ @Test
+ fun setRecommendationsLoadingState() =
+ testScope.runTest {
+ val recommendationsLoadingState by
+ collectLastValue(underTest.recommendationsLoadingState)
+ val recommendationsLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE)
+
+ underTest.setRecommedationsLoadingState(recommendationsLoadingModel)
+
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ }
+
companion object {
private const val KEY = "KEY"
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index d9d84f2d2aac..a0a1eb3a6ca6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
@@ -20,6 +20,7 @@ import android.R
import android.graphics.drawable.Icon
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
@@ -28,13 +29,20 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestHelper
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
+import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,9 +53,17 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val mediaDataFilter: MediaDataFilterImpl = kosmos.mediaDataFilter
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository
+
private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
@Test
fun addUserMediaEntry_activeThenInactivate() =
testScope.runTest {
@@ -56,7 +72,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
- val userMedia = MediaData().copy(active = true)
+ val userMedia = MediaData(active = true)
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
@@ -79,7 +95,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
val hasActiveMedia by collectLastValue(underTest.hasActiveMedia)
val hasAnyMedia by collectLastValue(underTest.hasAnyMedia)
- val userMedia = MediaData().copy(active = false)
+ val userMedia = MediaData(active = false)
val instanceId = userMedia.instanceId
mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
@@ -112,7 +128,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
isActive = true,
recommendations = MediaTestHelper.getValidRecommendationList(icon),
)
- val userMedia = MediaData().copy(active = false)
+ val userMedia = MediaData(active = false)
mediaFilterRepository.setRecommendation(userMediaRecommendation)
@@ -199,7 +215,80 @@ class MediaCarouselInteractorTest : SysuiTestCase() {
fun hasActiveMediaOrRecommendation_nothingSet_returnsFalse() =
testScope.runTest { assertThat(underTest.hasActiveMediaOrRecommendation.value).isFalse() }
+ @Test
+ fun onMediaDataUpdated_updatesLoadingState() =
+ testScope.runTest {
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val mediaDataLoadedStates by collectLastValue(underTest.mediaDataLoadedStates)
+ val instanceId = InstanceId.fakeInstanceId(123)
+ val mediaLoadedStates: MutableList<MediaDataLoadingModel> = mutableListOf()
+
+ mediaLoadedStates.add(MediaDataLoadingModel.Loaded(instanceId))
+ mediaDataFilter.onMediaDataLoaded(
+ KEY,
+ KEY,
+ MediaData(userId = USER_ID, instanceId = instanceId)
+ )
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ val newInstanceId = InstanceId.fakeInstanceId(321)
+
+ mediaLoadedStates.add(MediaDataLoadingModel.Loaded(newInstanceId))
+ mediaDataFilter.onMediaDataLoaded(
+ KEY_2,
+ KEY_2,
+ MediaData(userId = USER_ID, instanceId = newInstanceId)
+ )
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(instanceId))
+
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+
+ mediaLoadedStates.remove(MediaDataLoadingModel.Loaded(newInstanceId))
+
+ mediaDataFilter.onMediaDataRemoved(KEY_2)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStates)
+ }
+
+ @Test
+ fun onMediaRecommendationsUpdated_updatesLoadingState() =
+ testScope.runTest {
+ whenever(notificationLockscreenUserManager.isCurrentProfile(USER_ID)).thenReturn(true)
+ whenever(notificationLockscreenUserManager.isProfileAvailable(USER_ID)).thenReturn(true)
+ val recommendationsLoadingState by
+ collectLastValue(underTest.recommendationsLoadingState)
+ val icon = Icon.createWithResource(context, R.drawable.ic_media_play)
+ val mediaRecommendations =
+ SmartspaceMediaData(
+ targetId = KEY_MEDIA_SMARTSPACE,
+ isActive = true,
+ recommendations = MediaTestHelper.getValidRecommendationList(icon),
+ )
+ var recommendationsLoadingModel: SmartspaceMediaLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, isPrioritized = true)
+
+ mediaDataFilter.onSmartspaceMediaDataLoaded(KEY_MEDIA_SMARTSPACE, mediaRecommendations)
+
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+
+ recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(KEY_MEDIA_SMARTSPACE)
+
+ mediaDataFilter.onSmartspaceMediaDataRemoved(KEY_MEDIA_SMARTSPACE)
+
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ }
+
companion object {
+ private const val KEY = "key"
+ private const val KEY_2 = "key2"
+ private const val USER_ID = 0
private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID"
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index ae2fefd7b957..61adcd2e2c25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -290,6 +290,38 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ fun switchFromBouncerToQuickSettingsWhenDeviceUnlocked() =
+ testScope.runTest {
+ val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
+
+ val transitionState =
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ isDeviceUnlocked = false,
+ initialSceneKey = Scenes.Lockscreen,
+ )
+ assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
+ underTest.start()
+ runCurrent()
+
+ sceneInteractor.changeScene(Scenes.QuickSettings, "switching to qs for test")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
+ runCurrent()
+ assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+
+ sceneInteractor.changeScene(Scenes.Bouncer, "switching to bouncer for test")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.Bouncer)
+ runCurrent()
+ assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+
+ assertThat(currentSceneKey).isEqualTo(Scenes.QuickSettings)
+ }
+
+ @Test
fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
testScope.runTest {
val currentSceneKey by collectLastValue(sceneInteractor.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index 757a6c9e5ac6..5b33ecbb28be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -641,7 +641,6 @@ class ShadeInteractorImplTest : SysuiTestCase() {
)
)
val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
- runCurrent()
assertThat(isShadeTouchable).isFalse()
}
@@ -668,13 +667,17 @@ class ShadeInteractorImplTest : SysuiTestCase() {
)
)
val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
- runCurrent()
assertThat(isShadeTouchable).isTrue()
}
@Test
fun isShadeTouchable_isFalse_whenStartingToSleepAndNotControlScreenOff() =
testScope.runTest {
+ whenever(dozeParameters.shouldControlScreenOff()).thenReturn(false)
+ val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+ // Assert the default condition is true
+ assertThat(isShadeTouchable).isTrue()
+
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -688,15 +691,17 @@ class ShadeInteractorImplTest : SysuiTestCase() {
transitionState = TransitionState.STARTED,
)
)
- whenever(dozeParameters.shouldControlScreenOff()).thenReturn(false)
- val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
- runCurrent()
assertThat(isShadeTouchable).isFalse()
}
@Test
fun isShadeTouchable_isTrue_whenStartingToSleepAndControlScreenOff() =
testScope.runTest {
+ whenever(dozeParameters.shouldControlScreenOff()).thenReturn(true)
+ val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
+ // Assert the default condition is true
+ assertThat(isShadeTouchable).isTrue()
+
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -710,9 +715,6 @@ class ShadeInteractorImplTest : SysuiTestCase() {
transitionState = TransitionState.STARTED,
)
)
- whenever(dozeParameters.shouldControlScreenOff()).thenReturn(true)
- val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
- runCurrent()
assertThat(isShadeTouchable).isTrue()
}
@@ -730,7 +732,6 @@ class ShadeInteractorImplTest : SysuiTestCase() {
)
)
val isShadeTouchable by collectLastValue(underTest.isShadeTouchable)
- runCurrent()
assertThat(isShadeTouchable).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 3c9dc6345d31..69207ba07e6e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -89,7 +89,7 @@ class TestableHeadsUpManager extends BaseHeadsUpManager {
}
@Override
- public boolean isHeadsUpGoingAway() {
+ public boolean isHeadsUpAnimatingAwayValue() {
throw new UnsupportedOperationException();
}
@@ -115,7 +115,7 @@ class TestableHeadsUpManager extends BaseHeadsUpManager {
}
@Override
- public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+ public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
throw new UnsupportedOperationException();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.kt
new file mode 100644
index 000000000000..8bb36724d1d8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartableTest.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.systemui.volume.domain.startable
+
+import android.media.AudioManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.audioModeInteractor
+import com.android.systemui.volume.audioRepository
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioModeLoggerStartableTest : SysuiTestCase() {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: AudioModeLoggerStartable
+
+ @Before
+ fun setUp() {
+ with(kosmos) {
+ underTest =
+ AudioModeLoggerStartable(
+ applicationCoroutineScope,
+ uiEventLogger,
+ audioModeInteractor
+ )
+ }
+ }
+
+ @Test
+ fun audioMode_inCall() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setMode(AudioManager.MODE_IN_CALL)
+
+ underTest.start()
+ runCurrent()
+
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING.id)
+ }
+ }
+ }
+
+ @Test
+ fun audioMode_notInCall() {
+ with(kosmos) {
+ testScope.runTest {
+ audioRepository.setMode(AudioManager.MODE_NORMAL)
+
+ underTest.start()
+ runCurrent()
+
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL.id)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
index 2cc1ad335535..27a813fb149e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt
@@ -21,6 +21,8 @@ import android.content.Intent
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.logging.uiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -29,6 +31,7 @@ import com.android.systemui.plugins.activityStarter
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -58,7 +61,10 @@ class BottomBarViewModelTest : SysuiTestCase() {
private lateinit var underTest: BottomBarViewModel
private fun initUnderTest() {
- underTest = with(kosmos) { BottomBarViewModel(activityStarter, volumePanelViewModel) }
+ underTest =
+ with(kosmos) {
+ BottomBarViewModel(activityStarter, volumePanelViewModel, uiEventLogger)
+ }
}
@Test
@@ -96,6 +102,8 @@ class BottomBarViewModelTest : SysuiTestCase() {
/* userHandle = */ eq(null),
)
assertThat(intentCaptor.value.action).isEqualTo(Settings.ACTION_SOUND_SETTINGS)
+ assertThat(uiEventLoggerFake.eventId(0))
+ .isEqualTo(VolumePanelUiEvent.VOLUME_PANEL_SOUND_SETTINGS_CLICKED.id)
activityStartedCaptor.value.onActivityStarted(ActivityManager.START_SUCCESS)
val volumePanelState by collectLastValue(volumePanelViewModel.volumePanelState)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
index 610195f5e87e..fdeded8422d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModelTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.panel.component.captioning.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -45,7 +46,12 @@ class CaptioningViewModelTest : SysuiTestCase() {
fun setup() {
underTest =
with(kosmos) {
- CaptioningViewModel(context, captioningInteractor, testScope.backgroundScope)
+ CaptioningViewModel(
+ context,
+ captioningInteractor,
+ testScope.backgroundScope,
+ uiEventLogger,
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
index ec55c75d4ae5..da0a2295143b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/MediaOutputAvailabilityCriteriaTest.kt
@@ -46,7 +46,10 @@ class MediaOutputAvailabilityCriteriaTest : SysuiTestCase() {
@Before
fun setup() {
- underTest = MediaOutputAvailabilityCriteria(kosmos.audioModeInteractor)
+ underTest =
+ MediaOutputAvailabilityCriteria(
+ kosmos.audioModeInteractor,
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 462f36d73138..30524d93dc02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -22,6 +22,7 @@ import android.media.session.PlaybackState
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -64,6 +65,7 @@ class MediaOutputViewModelTest : SysuiTestCase() {
mediaOutputActionsInteractor,
mediaDeviceSessionInteractor,
mediaOutputInteractor,
+ uiEventLogger,
)
with(context.orCreateTestableResources) {
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index af327d2b7791..26fa2b136ed4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -613,7 +613,7 @@
<dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
<dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
- <dimen name="volume_panel_corner_radius">52dp</dimen>
+ <dimen name="volume_panel_corner_radius">28dp</dimen>
<!-- Size of each item in the ringer selector drawer. -->
<dimen name="volume_ringer_drawer_item_size">42dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 3b8a268cd5ea..460779c73cda 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -15,6 +15,7 @@
*/
package com.android.keyguard
+import android.os.Trace
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -430,6 +431,7 @@ constructor(
if (MigrateClocksToBlueprint.isEnabled) {
listenForDozeAmountTransition(this)
listenForAnyStateToAodTransition(this)
+ listenForAnyStateToLockscreenTransition(this)
} else {
listenForDozeAmount(this)
}
@@ -520,8 +522,12 @@ constructor(
private fun handleDoze(doze: Float) {
dozeAmount = doze
clock?.run {
+ Trace.beginSection("$TAG#smallClock.animations.doze")
smallClock.animations.doze(dozeAmount)
+ Trace.endSection()
+ Trace.beginSection("$TAG#largeClock.animations.doze")
largeClock.animations.doze(dozeAmount)
+ Trace.endSection()
}
smallTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
largeTimeListener?.update(doze < DOZE_TICKRATE_THRESHOLD)
@@ -536,10 +542,10 @@ constructor(
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
merge(
- keyguardTransitionInteractor.aodToLockscreenTransition.map { step ->
+ keyguardTransitionInteractor.transition(AOD, LOCKSCREEN).map { step ->
step.copy(value = 1f - step.value)
},
- keyguardTransitionInteractor.lockscreenToAodTransition,
+ keyguardTransitionInteractor.transition(LOCKSCREEN, AOD),
).filter {
it.transitionState != TransitionState.FINISHED
}
@@ -562,6 +568,17 @@ constructor(
}
@VisibleForTesting
+ internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job {
+ return scope.launch {
+ keyguardTransitionInteractor
+ .transitionStepsToState(LOCKSCREEN)
+ .filter { it.transitionState == TransitionState.STARTED }
+ .filter { it.from != AOD }
+ .collect { handleDoze(0f) }
+ }
+ }
+
+ @VisibleForTesting
internal fun listenForDozing(scope: CoroutineScope): Job {
return scope.launch {
combine(
@@ -628,7 +645,7 @@ constructor(
}
companion object {
- private val TAG = ClockEventController::class.simpleName!!
- private val DOZE_TICKRATE_THRESHOLD = 0.99f
+ private const val TAG = "ClockEventController"
+ private const val DOZE_TICKRATE_THRESHOLD = 0.99f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index ec54e4ce5e86..9816896e3ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -282,7 +282,8 @@ open class UdfpsKeyguardViewControllerLegacy(
@VisibleForTesting
suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
- transitionInteractor.goneToAodTransition.collect { transitionStep ->
+ transitionInteractor.transition(KeyguardState.GONE, KeyguardState.AOD).collect {
+ transitionStep ->
view.onDozeAmountChanged(
transitionStep.value,
transitionStep.value,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index 7525ce0f98ac..fa19bf478453 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -27,9 +27,9 @@ import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.time.SystemClock
import dagger.Lazy
import javax.inject.Inject
@@ -78,15 +78,14 @@ constructor(
bouncerRepository.alternateBouncerUIAvailable
}
private val isDozingOrAod: Flow<Boolean> =
- keyguardTransitionInteractor
- .get()
- .transitions
- .map {
- it.to == KeyguardState.DOZING ||
- it.to == KeyguardState.AOD ||
- ((it.from == KeyguardState.DOZING || it.from == KeyguardState.AOD) &&
- it.transitionState != TransitionState.FINISHED)
- }
+ or(
+ keyguardTransitionInteractor.get().transitionValue(KeyguardState.DOZING).map {
+ it > 0f
+ },
+ keyguardTransitionInteractor.get().transitionValue(KeyguardState.AOD).map {
+ it > 0f
+ },
+ )
.distinctUntilChanged()
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index aeb564d53195..02a40d93ab65 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.domain.interactor
+import com.android.compose.animation.scene.SceneKey
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
@@ -26,6 +27,8 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
@@ -47,6 +50,7 @@ constructor(
private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
+ sceneInteractor: SceneInteractor,
) {
private val _onIncorrectBouncerInput = MutableSharedFlow<Unit>()
val onIncorrectBouncerInput: SharedFlow<Unit> = _onIncorrectBouncerInput
@@ -80,6 +84,10 @@ constructor(
}
.map {}
+ /** The scene to show when bouncer is dismissed. */
+ val dismissDestination: Flow<SceneKey> =
+ sceneInteractor.previousScene.map { it ?: Scenes.Lockscreen }
+
/** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
fun onDown() {
falsingInteractor.avoidGesture()
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 5c07cc57c620..7c41b75d7105 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -21,6 +21,12 @@ import android.app.admin.DevicePolicyResources
import android.content.Context
import android.graphics.Bitmap
import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
@@ -35,6 +41,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
@@ -82,6 +89,15 @@ class BouncerViewModel(
initialValue = null,
)
+ val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
+ bouncerInteractor.dismissDestination
+ .map(::destinationSceneMap)
+ .stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ initialValue = destinationSceneMap(Scenes.Lockscreen),
+ )
+
val message: BouncerMessageViewModel = bouncerMessageViewModel
val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
@@ -310,8 +326,7 @@ class BouncerViewModel(
{ message },
failedAttempts,
remainingAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -328,8 +343,7 @@ class BouncerViewModel(
.KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
{ message },
failedAttempts,
- )
- ?: message
+ ) ?: message
} else {
message
}
@@ -357,6 +371,12 @@ class BouncerViewModel(
}
}
+ private fun destinationSceneMap(prevScene: SceneKey) =
+ mapOf(
+ Back to UserActionResult(prevScene),
+ Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
+ )
+
data class DialogViewModel(
val text: String,
@@ -400,13 +420,13 @@ object BouncerViewModelModule {
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
selectedUserInteractor = selectedUserInteractor,
+ devicePolicyManager = devicePolicyManager,
+ bouncerMessageViewModel = bouncerMessageViewModel,
flags = flags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
userSwitcherMenu = userSwitcherViewModel.menu,
actionButton = actionButtonInteractor.actionButton,
- devicePolicyManager = devicePolicyManager,
- bouncerMessageViewModel = bouncerMessageViewModel,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 1eba0662a43d..a8f30297ff07 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -295,7 +295,8 @@ constructor(
}
private fun listenForSchedulingWatchdog() {
- keyguardTransitionInteractor.anyStateToGoneTransition
+ keyguardTransitionInteractor
+ .transition(from = null, to = KeyguardState.GONE)
.filter { it.transitionState == TransitionState.FINISHED }
.onEach {
// We deliberately want to run this in background because scheduleWatchdog does
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index a7266503b7a1..03819ed9e2fe 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -37,6 +37,10 @@ import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStat
import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -121,9 +125,9 @@ constructor(
.launchIn(applicationScope)
merge(
- keyguardTransitionInteractor.aodToLockscreenTransition,
- keyguardTransitionInteractor.offToLockscreenTransition,
- keyguardTransitionInteractor.dozingToLockscreenTransition
+ keyguardTransitionInteractor.transition(AOD, LOCKSCREEN),
+ keyguardTransitionInteractor.transition(OFF, LOCKSCREEN),
+ keyguardTransitionInteractor.transition(DOZING, LOCKSCREEN),
)
.filter { it.transitionState == TransitionState.STARTED }
.sample(powerInteractor.detailedWakefulness)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index ac03463da545..04edd252f98f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -23,6 +23,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
@@ -97,7 +98,7 @@ constructor(
.distinctUntilChanged()
val transitionEnded =
- keyguardTransitionInteractor.fromDreamingTransition.filter { step ->
+ keyguardTransitionInteractor.transition(from = DREAMING, to = null).filter { step ->
step.transitionState == TransitionState.FINISHED ||
step.transitionState == TransitionState.CANCELED
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 6b53f4ed554f..a5d7e04bf4d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -63,6 +63,7 @@ import android.view.SurfaceControl;
import android.view.WindowManagerPolicyConstants;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
+import android.window.RemoteTransitionStub;
import android.window.TransitionInfo;
import com.android.internal.annotations.GuardedBy;
@@ -187,7 +188,7 @@ public class KeyguardService extends Service {
// Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
public static IRemoteTransition wrap(final KeyguardViewMediator keyguardViewMediator,
final IRemoteAnimationRunner runner) {
- return new IRemoteTransition.Stub() {
+ return new RemoteTransitionStub() {
@GuardedBy("mLeashMap")
private final ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = new ArrayMap<>();
@@ -253,11 +254,6 @@ public class KeyguardService extends Service {
}
}
- @Override
- public void onTransitionConsumed(IBinder transition, boolean aborted) {
- // No-op.
- }
-
private static void initAlphaForAnimationTargets(@NonNull SurfaceControl.Transaction t,
@NonNull RemoteAnimationTarget[] targets) {
for (RemoteAnimationTarget target : targets) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index e101b0ab64aa..c835599abfe1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -29,6 +29,7 @@ import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.utils.GlobalWindowManager
@@ -83,7 +84,7 @@ constructor(
applicationScope.launch(bgDispatcher) {
// We drop 1 to avoid triggering on initial collect().
- keyguardTransitionInteractor.anyStateToGoneTransition.collect { transition ->
+ keyguardTransitionInteractor.transition(from = null, to = GONE).collect { transition ->
if (transition.transitionState == TransitionState.FINISHED) {
onKeyguardGone()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 97081d93892a..d3ad0c2ac7e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,19 +20,14 @@ package com.android.systemui.keyguard.domain.interactor
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
-import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
-import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
-import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
-import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -40,7 +35,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
@@ -53,6 +47,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -64,7 +59,6 @@ class KeyguardTransitionInteractor
@Inject
constructor(
@Application val scope: CoroutineScope,
- @Main private val mainDispatcher: CoroutineDispatcher,
private val keyguardRepository: KeyguardRepository,
private val repository: KeyguardTransitionRepository,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
@@ -75,14 +69,13 @@ constructor(
dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
) {
- private val TAG = this::class.simpleName
-
- private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
+ private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>()
/**
* Numerous flows are derived from, or care directly about, the transition value in and out of a
* single state. This prevent the redundant filters from running.
*/
+ private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>()
private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> {
return transitionValueCache.getOrPut(state) {
MutableSharedFlow<Float>(
@@ -94,6 +87,7 @@ constructor(
}
}
+ @Deprecated("Not performant - Use something else in this class")
val transitions = repository.transitions
/**
@@ -106,14 +100,14 @@ constructor(
* from when we were canceled.
*/
val startedStepWithPrecedingStep =
- transitions
+ repository.transitions
.pairwise()
.filter { it.newValue.transitionState == TransitionState.STARTED }
.shareIn(scope, SharingStarted.Eagerly)
init {
// Collect non-canceled steps and emit transition values.
- scope.launch(mainDispatcher) {
+ scope.launch {
repository.transitions
.filter { it.transitionState != TransitionState.CANCELED }
.collect { step ->
@@ -122,11 +116,22 @@ constructor(
}
}
+ scope.launch {
+ repository.transitions.collect {
+ // FROM->TO
+ transitionMap[Edge(it.from, it.to)]?.emit(it)
+ // FROM->(ANY)
+ transitionMap[Edge(it.from, null)]?.emit(it)
+ // (ANY)->TO
+ transitionMap[Edge(null, it.to)]?.emit(it)
+ }
+ }
+
// If a transition from state A -> B is canceled in favor of a transition from B -> C, we
// need to ensure we emit transitionValue(A) = 0f, since no further steps will be emitted
// where the from or to states are A. This would leave transitionValue(A) stuck at an
// arbitrary non-zero value.
- scope.launch(mainDispatcher) {
+ scope.launch {
startedStepWithPrecedingStep.collect { (prevStep, startedStep) ->
if (
prevStep.transitionState == TransitionState.CANCELED &&
@@ -138,116 +143,42 @@ constructor(
}
}
- /** (any)->GONE transition information */
- val anyStateToGoneTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == GONE }
-
- /** (any)->AOD transition information */
- val anyStateToAodTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == AOD }
-
- /** DREAMING->(any) transition information. */
- val fromDreamingTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.from == DREAMING }
-
- /** LOCKSCREEN->(any) transition information. */
- val fromLockscreenTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.from == LOCKSCREEN }
-
- /** (any)->Lockscreen transition information */
- val anyStateToLockscreenTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == LOCKSCREEN }
-
- /** (any)->Occluded transition information */
- val anyStateToOccludedTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == OCCLUDED }
-
- /** (any)->PrimaryBouncer transition information */
- val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER }
-
- /** (any)->Dreaming transition information */
- val anyStateToDreamingTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == DREAMING }
-
- /** (any)->AlternateBouncer transition information */
- val anyStateToAlternateBouncerTransition: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER }
-
- /** AOD->LOCKSCREEN transition information. */
- val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
-
- /** DREAMING->LOCKSCREEN transition information. */
- val dreamingToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(DREAMING, LOCKSCREEN)
-
- /** DREAMING_LOCKSCREEN_HOSTED->LOCKSCREEN transition information. */
- val dreamingLockscreenHostedToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN)
-
- /** GONE->AOD transition information. */
- val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD)
-
- /** GONE->DREAMING transition information. */
- val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING)
-
- /** GONE->DREAMING_LOCKSCREEN_HOSTED transition information. */
- val goneToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
- repository.transition(GONE, DREAMING_LOCKSCREEN_HOSTED)
-
- /** GONE->LOCKSCREEN transition information. */
- val goneToLockscreenTransition: Flow<TransitionStep> = repository.transition(GONE, LOCKSCREEN)
-
- /** LOCKSCREEN->AOD transition information. */
- val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
-
- /** LOCKSCREEN->DOZING transition information. */
- val lockscreenToDozingTransition: Flow<TransitionStep> =
- repository.transition(LOCKSCREEN, DOZING)
-
- /** LOCKSCREEN->DREAMING transition information. */
- val lockscreenToDreamingTransition: Flow<TransitionStep> =
- repository.transition(LOCKSCREEN, DREAMING)
-
- /** LOCKSCREEN->DREAMING_LOCKSCREEN_HOSTED transition information. */
- val lockscreenToDreamingLockscreenHostedTransition: Flow<TransitionStep> =
- repository.transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)
-
- /** LOCKSCREEN->GLANCEABLE_HUB transition information. */
- val lockscreenToGlanceableHubTransition: Flow<TransitionStep> =
- repository.transition(LOCKSCREEN, GLANCEABLE_HUB)
-
- /** LOCKSCREEN->OCCLUDED transition information. */
- val lockscreenToOccludedTransition: Flow<TransitionStep> =
- repository.transition(LOCKSCREEN, OCCLUDED)
-
- /** GLANCEABLE_HUB->LOCKSCREEN transition information. */
- val glanceableHubToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(GLANCEABLE_HUB, LOCKSCREEN)
-
- /** OCCLUDED->LOCKSCREEN transition information. */
- val occludedToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(OCCLUDED, LOCKSCREEN)
-
- /** PRIMARY_BOUNCER->GONE transition information. */
- val primaryBouncerToGoneTransition: Flow<TransitionStep> =
- repository.transition(PRIMARY_BOUNCER, GONE)
-
- /** OFF->LOCKSCREEN transition information. */
- val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
+ /** Given an [edge], return a SharedFlow to collect only relevant [TransitionStep]. */
+ fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> {
+ return transitionMap.getOrPut(edge) {
+ MutableSharedFlow<TransitionStep>(
+ extraBufferCapacity = 10,
+ onBufferOverflow = BufferOverflow.DROP_OLDEST
+ )
+ }
+ }
- /** DOZING->LOCKSCREEN transition information. */
- val dozingToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(DOZING, LOCKSCREEN)
+ /**
+ * Receive all [TransitionStep] matching a filter of [from]->[to]. Allow nulls in order to match
+ * any transition, for instance (any)->GONE.
+ */
+ fun transition(from: KeyguardState?, to: KeyguardState?): Flow<TransitionStep> {
+ if (from == null && to == null) {
+ throw IllegalArgumentException("from and to cannot both be null")
+ }
+ return getOrCreateFlow(Edge(from = from, to = to))
+ }
- /** Receive all [TransitionStep] matching a filter of [from]->[to] */
- fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
- return repository.transition(from, to)
+ /**
+ * The amount of transition into or out of the given [KeyguardState].
+ *
+ * The value will be `0` (or close to `0`, due to float point arithmetic) if not in this step or
+ * `1` when fully in the given state.
+ */
+ fun transitionValue(
+ state: KeyguardState,
+ ): Flow<Float> {
+ return getTransitionValueFlow(state)
}
/**
- * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
- * Lockscreen (0f).
+ * AOD<->* transition information, mapped to dozeAmount range of AOD (1f) <->
+ * * (0f).
*/
val dozeAmountTransition: Flow<TransitionStep> =
repository.transitions
@@ -265,13 +196,10 @@ constructor(
val startedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
- /** The last [TransitionStep] with a [TransitionState] of CANCELED */
- val canceledKeyguardTransitionStep: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.transitionState == TransitionState.CANCELED }
-
/** The last [TransitionStep] with a [TransitionState] of FINISHED */
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
- repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+ repository.transitions
+ .filter { step -> step.transitionState == TransitionState.FINISHED }
/** The destination state of the last [TransitionState.STARTED] transition. */
val startedKeyguardState: SharedFlow<KeyguardState> =
@@ -364,10 +292,6 @@ constructor(
* case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace
* during the LS -> GONE transition.
*
- * If you need special-case handling for cancellations (such as conditional handling depending
- * on which [KeyguardState] was canceled) you can collect [canceledKeyguardTransitionStep]
- * directly.
- *
* As a helpful footnote, here's the values of [finishedKeyguardState] and
* [currentKeyguardState] during a sequence with two cancellations:
* 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE.
@@ -390,7 +314,7 @@ constructor(
}
}
.distinctUntilChanged()
- .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
/**
* The [TransitionInfo] of the most recent call to
@@ -420,24 +344,12 @@ constructor(
/** Whether we've currently STARTED a transition and haven't yet FINISHED it. */
val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
- /**
- * The amount of transition into or out of the given [KeyguardState].
- *
- * The value will be `0` (or close to `0`, due to float point arithmetic) if not in this step or
- * `1` when fully in the given state.
- */
- fun transitionValue(
- state: KeyguardState,
- ): Flow<Float> {
- return getTransitionValueFlow(state)
- }
-
fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
- return repository.transitions.filter { step -> step.from == fromState }
+ return getOrCreateFlow(Edge(from = fromState, to = null))
}
fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
- return repository.transitions.filter { step -> step.to == toState }
+ return getOrCreateFlow(Edge(from = null, to = toState))
}
/**
@@ -464,17 +376,24 @@ constructor(
fun isInTransitionToState(
state: KeyguardState,
): Flow<Boolean> {
- return isInTransitionToStateWhere { it == state }
+ return getOrCreateFlow(Edge(from = null, to = state))
+ .mapLatest { it.transitionState.isActive() }
+ .onStart { emit(false) }
+ .distinctUntilChanged()
}
/**
- * Whether we're in a transition to a [KeyguardState] that matches the given predicate, but
- * haven't yet completed it.
+ * Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet
+ * completed it.
*/
- fun isInTransitionToStateWhere(
- stateMatcher: (KeyguardState) -> Boolean,
+ fun isInTransition(
+ from: KeyguardState,
+ to: KeyguardState,
): Flow<Boolean> {
- return isInTransitionWhere(fromStatePredicate = { true }, toStatePredicate = stateMatcher)
+ return getOrCreateFlow(Edge(from = from, to = to))
+ .mapLatest { it.transitionState.isActive() }
+ .onStart { emit(false) }
+ .distinctUntilChanged()
}
/**
@@ -483,12 +402,29 @@ constructor(
fun isInTransitionFromState(
state: KeyguardState,
): Flow<Boolean> {
- return isInTransitionFromStateWhere { it == state }
+ return getOrCreateFlow(Edge(from = state, to = null))
+ .mapLatest { it.transitionState.isActive() }
+ .onStart { emit(false) }
+ .distinctUntilChanged()
+ }
+
+ /**
+ * Whether we're in a transition to a [KeyguardState] that matches the given predicate, but
+ * haven't yet completed it.
+ *
+ * If you only care about a single state, instead use the optimized [isInTransitionToState].
+ */
+ fun isInTransitionToStateWhere(
+ stateMatcher: (KeyguardState) -> Boolean,
+ ): Flow<Boolean> {
+ return isInTransitionWhere(fromStatePredicate = { true }, toStatePredicate = stateMatcher)
}
/**
* Whether we're in a transition out of a [KeyguardState] that matches the given predicate, but
* haven't yet completed it.
+ *
+ * If you only care about a single state, instead use the optimized [isInTransitionFromState].
*/
fun isInTransitionFromStateWhere(
stateMatcher: (KeyguardState) -> Boolean,
@@ -499,6 +435,9 @@ constructor(
/**
* Whether we're in a transition between two [KeyguardState]s that match the given predicates,
* but haven't yet completed it.
+ *
+ * If you only care about a single state for both from and to, instead use the optimized
+ * [isInTransition].
*/
fun isInTransitionWhere(
fromStatePredicate: (KeyguardState) -> Boolean,
@@ -507,6 +446,13 @@ constructor(
return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) }
}
+ /**
+ * Whether we're in a transition between two [KeyguardState]s that match the given predicates,
+ * but haven't yet completed it.
+ *
+ * If you only care about a single state for both from and to, instead use the optimized
+ * [isInTransition].
+ */
fun isInTransitionWhere(
fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
): Flow<Boolean> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
index 38a93b50ea97..f6567a6ccb53 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -18,11 +18,21 @@ package com.android.systemui.keyguard.shared.model
/** Possible states for a running transition between [State] */
enum class TransitionState {
/* Transition has begun. */
- STARTED,
+ STARTED {
+ override fun isActive() = true
+ },
/* Transition is actively running. */
- RUNNING,
+ RUNNING {
+ override fun isActive() = true
+ },
/* Transition has completed successfully. */
- FINISHED,
+ FINISHED {
+ override fun isActive() = false
+ },
/* Transition has been interrupted, and not completed successfully. */
- CANCELED,
+ CANCELED {
+ override fun isActive() = false
+ };
+
+ abstract fun isActive(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 5de1a61d61b5..735b10907c73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -19,8 +19,6 @@ import android.view.animation.Interpolator
import com.android.app.animation.Interpolators.LINEAR
import com.android.keyguard.logging.KeyguardTransitionAnimationLogger
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -35,15 +33,10 @@ import kotlin.math.max
import kotlin.math.min
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.launch
/**
* Assists in creating sub-flows for a KeyguardTransition. Call [setup] once for a transition, and
@@ -53,35 +46,9 @@ import kotlinx.coroutines.launch
class KeyguardTransitionAnimationFlow
@Inject
constructor(
- @Application private val scope: CoroutineScope,
- @Main private val mainDispatcher: CoroutineDispatcher,
private val transitionInteractor: KeyguardTransitionInteractor,
private val logger: KeyguardTransitionAnimationLogger,
) {
- private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>()
-
- init {
- scope.launch(mainDispatcher) {
- transitionInteractor.transitions.collect {
- // FROM->TO
- transitionMap[Edge(it.from, it.to)]?.emit(it)
- // FROM->(ANY)
- transitionMap[Edge(it.from, null)]?.emit(it)
- // (ANY)->TO
- transitionMap[Edge(null, it.to)]?.emit(it)
- }
- }
- }
-
- private fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> {
- return transitionMap.getOrPut(edge) {
- MutableSharedFlow<TransitionStep>(
- extraBufferCapacity = 10,
- onBufferOverflow = BufferOverflow.DROP_OLDEST
- )
- }
- }
-
/** Invoke once per transition between FROM->TO states to get access to a shared flow. */
fun setup(
duration: Duration,
@@ -185,7 +152,8 @@ constructor(
}?.let { onStep(interpolator.getInterpolation(it)) }
}
- return getOrCreateFlow(edge)
+ return transitionInteractor
+ .getOrCreateFlow(edge)
.map { step ->
StateToValue(
from = step.from,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 4d3a78d32b3a..91f76a4df771 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -169,10 +169,7 @@ class ClockSizeTransition(
return@OnPreDrawListener true
}
- anim.duration = duration
- anim.startDelay = startDelay
- anim.interpolator = interpolator
- anim.addListener(
+ val listener =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(anim: Animator) {
assignAnimValues("start", 0f, fromVis)
@@ -183,8 +180,21 @@ class ClockSizeTransition(
if (sendToBack) toView.translationZ = 0f
toView.viewTreeObserver.removeOnPreDrawListener(predrawCallback)
}
+
+ override fun onAnimationPause(anim: Animator) {
+ toView.viewTreeObserver.removeOnPreDrawListener(predrawCallback)
+ }
+
+ override fun onAnimationResume(anim: Animator) {
+ toView.viewTreeObserver.addOnPreDrawListener(predrawCallback)
+ }
}
- )
+
+ anim.duration = duration
+ anim.startDelay = startDelay
+ anim.interpolator = interpolator
+ anim.addListener(listener)
+ anim.addPauseListener(listener)
assignAnimValues("init", 0f, fromVis)
toView.viewTreeObserver.addOnPreDrawListener(predrawCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
index 7814576eff01..5cf100e78e6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt
@@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -38,12 +37,9 @@ constructor(
private val deviceSupportsAlternateBouncer: Flow<Boolean> =
alternateBouncerInteractor.alternateBouncerSupported
private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> =
- keyguardTransitionInteractor.transitions
- .map {
- it.to == KeyguardState.ALTERNATE_BOUNCER ||
- (it.from == KeyguardState.ALTERNATE_BOUNCER &&
- it.transitionState != TransitionState.FINISHED)
- }
+ keyguardTransitionInteractor
+ .transitionValue(KeyguardState.ALTERNATE_BOUNCER)
+ .map { it > 0f }
.distinctUntilChanged()
val alternateBouncerWindowRequired: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 4ddd57110b38..e2177e61d954 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -29,9 +29,6 @@ import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
-import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.res.R
import javax.inject.Inject
@@ -97,9 +94,9 @@ constructor(
occludedToLockscreen,
aodToLockscreen ->
val translationY =
- if (isInTransition(aodToLockscreen.transitionState)) {
+ if (aodToLockscreen.transitionState.isActive()) {
aodToLockscreen.value ?: 0f
- } else if (isInTransition(goneToAod.transitionState)) {
+ } else if (goneToAod.transitionState.isActive()) {
(goneToAod.value ?: 0f) + burnInModel.translationY
} else {
burnInModel.translationY + occludedToLockscreen + keyguardTranslationY
@@ -110,10 +107,6 @@ constructor(
.distinctUntilChanged()
}
- private fun isInTransition(state: TransitionState): Boolean {
- return state == STARTED || state == RUNNING
- }
-
private fun burnIn(
params: BurnInParameters,
): Flow<BurnInModel> {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
index 6d0f96c2f635..24429fae93ac 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt
@@ -102,7 +102,7 @@ constructor(
to = GONE,
)
- return shadeInteractor.shadeExpansion.flatMapLatest { shadeExpansion ->
+ return shadeInteractor.isAnyExpanded.flatMapLatest { isAnyExpanded ->
transitionAnimation
.sharedFlow(
duration = duration,
@@ -110,7 +110,7 @@ constructor(
onStart = {
leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
- isShadeExpanded = shadeExpansion > 0f
+ isShadeExpanded = isAnyExpanded
},
onStep = { 1f - it },
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index ac67f94fa9cf..24a7c512a6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -42,6 +42,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
@@ -133,22 +134,18 @@ constructor(
private val isOnLockscreen: Flow<Boolean> =
combine(
keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) },
- keyguardTransitionInteractor
- .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
- .onStart { emit(false) }
+ or(
+ keyguardTransitionInteractor.isInTransitionToState(LOCKSCREEN),
+ keyguardTransitionInteractor.isInTransitionFromState(LOCKSCREEN),
+ ),
) { onLockscreen, transitioningToOrFromLockscreen ->
onLockscreen || transitioningToOrFromLockscreen
}
.distinctUntilChanged()
- private val lockscreenToGoneTransitionRunning: Flow<Boolean> =
- keyguardTransitionInteractor
- .isInTransitionWhere { from, to -> from == LOCKSCREEN && to == GONE }
- .onStart { emit(false) }
-
private val alphaOnShadeExpansion: Flow<Float> =
combineTransform(
- lockscreenToGoneTransitionRunning,
+ keyguardTransitionInteractor.isInTransition(from = LOCKSCREEN, to = GONE),
isOnLockscreen,
shadeInteractor.qsExpansion,
shadeInteractor.shadeExpansion,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
index df34169c9a50..9dc5900296c2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt
@@ -19,7 +19,9 @@ package com.android.systemui.media.controls.data.repository
import com.android.internal.logging.InstanceId
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -46,6 +48,16 @@ class MediaFilterRepository @Inject constructor() {
MutableStateFlow(LinkedHashMap())
val allUserEntries: StateFlow<Map<String, MediaData>> = _allUserEntries.asStateFlow()
+ private val _mediaDataLoadedStates: MutableStateFlow<List<MediaDataLoadingModel>> =
+ MutableStateFlow(mutableListOf())
+ val mediaDataLoadedStates: StateFlow<List<MediaDataLoadingModel>> =
+ _mediaDataLoadedStates.asStateFlow()
+
+ private val _recommendationsLoadingState: MutableStateFlow<SmartspaceMediaLoadingModel> =
+ MutableStateFlow(SmartspaceMediaLoadingModel.Unknown)
+ val recommendationsLoadingState: StateFlow<SmartspaceMediaLoadingModel> =
+ _recommendationsLoadingState.asStateFlow()
+
fun addMediaEntry(key: String, data: MediaData) {
val entries = LinkedHashMap<String, MediaData>(_allUserEntries.value)
entries[key] = data
@@ -110,4 +122,25 @@ class MediaFilterRepository @Inject constructor() {
fun setReactivatedId(instanceId: InstanceId?) {
_reactivatedId.value = instanceId
}
+
+ fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) {
+ // Filter out previous loading state that has same [InstanceId].
+ val loadedStates =
+ _mediaDataLoadedStates.value.filter { loadedModel ->
+ loadedModel !is MediaDataLoadingModel.Loaded ||
+ !loadedModel.equalInstanceIds(mediaDataLoadingModel)
+ }
+
+ _mediaDataLoadedStates.value =
+ loadedStates +
+ if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) {
+ listOf(mediaDataLoadingModel)
+ } else {
+ emptyList()
+ }
+ }
+
+ fun setRecommedationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) {
+ _recommendationsLoadingState.value = smartspaceMediaLoadingModel
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index d40069c4b3da..a30e5826529a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -28,7 +28,9 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.settings.UserTracker
@@ -67,9 +69,6 @@ constructor(
private val mediaFlags: MediaFlags,
private val mediaFilterRepository: MediaFilterRepository,
) : MediaDataManager.Listener {
- private val _listeners: MutableSet<Listener> = mutableSetOf()
- val listeners: Set<Listener>
- get() = _listeners.toSet()
lateinit var mediaDataManager: MediaDataManager
// Ensure the field (and associated reference) isn't removed during optimization.
@@ -111,8 +110,9 @@ constructor(
mediaFilterRepository.addSelectedUserMediaEntry(data)
- // Notify listeners
- listeners.forEach { it.onMediaDataLoaded(data.instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(data.instanceId)
+ )
}
override fun onSmartspaceMediaDataLoaded(
@@ -159,7 +159,7 @@ constructor(
// reactivate.
if (shouldReactivate) {
val lastActiveId = sorted.lastKey() // most recently active id
- // Notify listeners to consider this media active
+ // Update loading state to consider this media active
Log.d(TAG, "reactivating $lastActiveId instead of smartspace")
mediaFilterRepository.setReactivatedId(lastActiveId)
val mediaData = sorted[lastActiveId]!!.copy(active = true)
@@ -168,15 +168,9 @@ constructor(
mediaData.packageName,
mediaData.instanceId
)
- listeners.forEach {
- it.onMediaDataLoaded(
- lastActiveId,
- receivedSmartspaceCardLatency =
- (systemClock.currentTimeMillis() - data.headphoneConnectionTimeMillis)
- .toInt(),
- isSsReactivated = true
- )
- }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(lastActiveId)
+ )
}
} else if (data.isActive) {
// Mark to prioritize Smartspace card if no recent media.
@@ -192,15 +186,18 @@ constructor(
smartspaceMediaData.packageName,
smartspaceMediaData.instanceId
)
- listeners.forEach { it.onSmartspaceMediaDataLoaded(key, shouldPrioritizeMutable) }
+ mediaFilterRepository.setRecommedationsLoadingState(
+ SmartspaceMediaLoadingModel.Loaded(key, shouldPrioritizeMutable)
+ )
}
override fun onMediaDataRemoved(key: String) {
mediaFilterRepository.removeMediaEntry(key)?.let { mediaData ->
val instanceId = mediaData.instanceId
mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let {
- // Only notify listeners if something actually changed
- listeners.forEach { it.onMediaDataRemoved(instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
}
}
}
@@ -210,11 +207,11 @@ constructor(
mediaFilterRepository.reactivatedId.value?.let { lastActiveId ->
mediaFilterRepository.setReactivatedId(null)
Log.d(TAG, "expiring reactivated key $lastActiveId")
- // Notify listeners to update with actual active value
+ // Update loading state with actual active value
mediaFilterRepository.selectedUserEntries.value[lastActiveId]?.let {
- listeners.forEach { listener ->
- listener.onMediaDataLoaded(lastActiveId, immediately)
- }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(lastActiveId, immediately)
+ )
}
}
@@ -227,7 +224,9 @@ constructor(
)
)
}
- listeners.forEach { it.onSmartspaceMediaDataRemoved(key, immediately) }
+ mediaFilterRepository.setRecommedationsLoadingState(
+ SmartspaceMediaLoadingModel.Removed(key, immediately)
+ )
}
@VisibleForTesting
@@ -238,29 +237,37 @@ constructor(
// Only remove media when the profile is unavailable.
if (DEBUG) Log.d(TAG, "Removing $key after profile change")
mediaFilterRepository.removeSelectedUserMediaEntry(data.instanceId, data)
- listeners.forEach { listener -> listener.onMediaDataRemoved(data.instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(data.instanceId)
+ )
}
}
}
@VisibleForTesting
internal fun handleUserSwitched() {
- // If the user changes, remove all current MediaData objects and inform listeners
- val listenersCopy = listeners
+ // If the user changes, remove all current MediaData objects.
val keyCopy = mediaFilterRepository.selectedUserEntries.value.keys.toMutableList()
- // Clear the list first, to make sure callbacks from listeners if we have any entries
- // are up to date
+ // Clear the list first and update loading state to remove media from UI.
mediaFilterRepository.clearSelectedUserMedia()
keyCopy.forEach { instanceId ->
if (DEBUG) Log.d(TAG, "Removing $instanceId after user change")
- listenersCopy.forEach { listener -> listener.onMediaDataRemoved(instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Removed(instanceId)
+ )
}
mediaFilterRepository.allUserEntries.value.forEach { (key, data) ->
if (lockscreenUserManager.isCurrentProfile(data.userId)) {
- if (DEBUG) Log.d(TAG, "Re-adding $key after user change")
+ if (DEBUG)
+ Log.d(
+ TAG,
+ "Re-adding $key with instanceId=${data.instanceId} after user change"
+ )
mediaFilterRepository.addSelectedUserMediaEntry(data)
- listenersCopy.forEach { listener -> listener.onMediaDataLoaded(data.instanceId) }
+ mediaFilterRepository.addMediaDataLoadingState(
+ MediaDataLoadingModel.Loaded(data.instanceId)
+ )
}
}
}
@@ -310,12 +317,6 @@ constructor(
}
}
- /** Add a listener for filtered [MediaData] changes */
- fun addListener(listener: Listener) = _listeners.add(listener)
-
- /** Remove a listener that was registered with addListener */
- fun removeListener(listener: Listener) = _listeners.remove(listener)
-
/**
* Return the time since last active for the most-recent media.
*
@@ -335,48 +336,6 @@ constructor(
return sortedEntries[lastActiveInstanceId]?.let { now - it.lastActive } ?: Long.MAX_VALUE
}
- interface Listener {
- /**
- * Called whenever there's new MediaData Loaded for the consumption in views.
- *
- * @param immediately indicates should apply the UI changes immediately, otherwise wait
- * until the next refresh-round before UI becomes visible. True by default to take in
- * place immediately.
- * @param receivedSmartspaceCardLatency is the latency between headphone connects and sysUI
- * displays Smartspace media targets. Will be 0 if the data is not activated by Smartspace
- * signal.
- * @param isSsReactivated indicates resume media card is reactivated by Smartspace
- * recommendation signal
- */
- fun onMediaDataLoaded(
- instanceId: InstanceId,
- immediately: Boolean = true,
- receivedSmartspaceCardLatency: Int = 0,
- isSsReactivated: Boolean = false,
- )
-
- /**
- * Called whenever there's new Smartspace media data loaded.
- *
- * @param shouldPrioritize indicates the sorting priority of the Smartspace card. If true,
- * it will be prioritized as the first card. Otherwise, it will show up as the last card
- * as default.
- */
- fun onSmartspaceMediaDataLoaded(key: String, shouldPrioritize: Boolean = false)
-
- /** Called whenever a previously existing Media notification was removed. */
- fun onMediaDataRemoved(instanceId: InstanceId)
-
- /**
- * Called whenever a previously existing Smartspace media data was removed.
- *
- * @param immediately indicates should apply the UI changes immediately, otherwise wait
- * until the next refresh-round before UI becomes visible. True by default to take in
- * place immediately.
- */
- fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean = true)
- }
-
companion object {
/**
* Maximum age of a media control to re-activate on smartspace signal. If there is no media
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 7dbca0ae4cda..cdcf3636e148 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -34,11 +34,14 @@ import com.android.systemui.media.controls.domain.pipeline.MediaDeviceManager
import com.android.systemui.media.controls.domain.pipeline.MediaSessionBasedFilter
import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.util.MediaFlags
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
@@ -109,6 +112,14 @@ constructor(
.distinctUntilChanged()
.stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+ /** The most recent list of loaded media controls. */
+ val mediaDataLoadedStates: Flow<List<MediaDataLoadingModel>> =
+ mediaFilterRepository.mediaDataLoadedStates
+
+ /** The most recent change to loaded media recommendations. */
+ val recommendationsLoadingState: Flow<SmartspaceMediaLoadingModel> =
+ mediaFilterRepository.recommendationsLoadingState
+
override fun start() {
if (!mediaFlags.isMediaControlsRefactorEnabled()) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
new file mode 100644
index 000000000000..bd42a4df7262
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+import com.android.internal.logging.InstanceId
+
+/** Models media data loading state. */
+sealed class MediaDataLoadingModel {
+ /** The initial loading state when no media data has yet loaded. */
+ data object Unknown : MediaDataLoadingModel()
+
+ /** Media data has been loaded. */
+ data class Loaded(
+ val instanceId: InstanceId,
+ val immediatelyUpdateUi: Boolean = true,
+ ) : MediaDataLoadingModel() {
+
+ /** Returns true if [other] has the same instance id, false otherwise. */
+ fun equalInstanceIds(other: MediaDataLoadingModel): Boolean {
+ return when (other) {
+ is Loaded -> other.instanceId == instanceId
+ is Removed -> other.instanceId == instanceId
+ Unknown -> false
+ }
+ }
+ }
+
+ /** Media data has been removed. */
+ data class Removed(
+ val instanceId: InstanceId,
+ ) : MediaDataLoadingModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt
new file mode 100644
index 000000000000..6c1e536f8c02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/SmartspaceMediaLoadingModel.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.shared.model
+
+/** Models smartspace media loading state. */
+sealed class SmartspaceMediaLoadingModel {
+ /** The initial loading state when no smartspace media has yet loaded. */
+ data object Unknown : SmartspaceMediaLoadingModel()
+
+ /** Smartspace media has been loaded. */
+ data class Loaded(
+ val key: String,
+ val isPrioritized: Boolean = false,
+ ) : SmartspaceMediaLoadingModel()
+
+ /** Smartspace media has been removed. */
+ data class Removed(
+ val key: String,
+ val immediatelyUpdateUi: Boolean = true,
+ ) : SmartspaceMediaLoadingModel()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
index 963c602b3d1e..c02ce3b0a6c0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaController.kt
@@ -297,6 +297,7 @@ constructor(
}
}
- private val activeContainer: ViewGroup? =
- if (useSplitShade) splitShadeContainer else singlePaneContainer
+ // This field is only used to log current active container.
+ private val activeContainer: ViewGroup?
+ get() = if (useSplitShade) splitShadeContainer else singlePaneContainer
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index c3c1e83546df..5b39ed34cb75 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -46,6 +46,8 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -600,7 +602,8 @@ constructor(
@VisibleForTesting
internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.anyStateToGoneTransition
+ keyguardTransitionInteractor
+ .transition(from = null, to = GONE)
.filter { it.transitionState == TransitionState.FINISHED }
.collect {
showMediaCarousel()
@@ -612,7 +615,8 @@ constructor(
@VisibleForTesting
internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job {
return scope.launch {
- keyguardTransitionInteractor.anyStateToLockscreenTransition
+ keyguardTransitionInteractor
+ .transition(from = null, to = LOCKSCREEN)
.filter { it.transitionState == TransitionState.FINISHED }
.collect {
if (!allowMediaPlayerOnLockScreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index 6e7e0f241dd8..da852348b4e6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -18,6 +18,7 @@ package com.android.systemui.media.dialog;
import android.annotation.MainThread;
import android.content.Context;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -52,8 +53,9 @@ public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue.
@Override
@MainThread
- public void showMediaOutputSwitcher(String packageName) {
+ public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {
if (!TextUtils.isEmpty(packageName)) {
+ // TODO: b/279555229 - Pass the userHandle into the output dialog manager.
mMediaOutputDialogManager.createAndShow(packageName, false, null);
} else {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 1f935f97e771..0e66c28d4b8d 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -62,6 +62,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -69,10 +70,12 @@ import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
@@ -246,6 +249,12 @@ constructor(
private fun handleDeviceUnlockStatus() {
applicationScope.launch {
+ // Track the previous scene (sans Bouncer), so that we know where to go when the device
+ // is unlocked whilst on the bouncer.
+ val previousScene =
+ sceneInteractor.previousScene
+ .filterNot { it == Scenes.Bouncer }
+ .stateIn(this, SharingStarted.Eagerly, initialValue = null)
deviceUnlockedInteractor.deviceUnlockStatus
.mapNotNull { deviceUnlockStatus ->
val renderedScenes =
@@ -273,8 +282,15 @@ constructor(
when {
isOnBouncer ->
- // When the device becomes unlocked in Bouncer, go to Gone.
- Scenes.Gone to "device was unlocked in Bouncer scene"
+ // When the device becomes unlocked in Bouncer, go to previous scene,
+ // or Gone.
+ if (previousScene.value == Scenes.Lockscreen) {
+ Scenes.Gone to "device was unlocked in Bouncer scene"
+ } else {
+ val prevScene = previousScene.value
+ (prevScene ?: Scenes.Gone) to
+ "device was unlocked in Bouncer scene, from sceneKey=$prevScene"
+ }
isOnLockscreen ->
// The lockscreen should be dismissed automatically in 2 scenarios:
// 1. When face auth bypass is enabled and authentication happens while
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 4660831b77af..6b08a9ae52f2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -30,6 +30,11 @@ import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.classifier.Classifier.UNLOCK;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadScroll;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
@@ -1119,7 +1124,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
controller.setup(mNotificationContainerParent));
// Dreaming->Lockscreen
- collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(DREAMING, LOCKSCREEN),
mDreamingToLockscreenTransition, mMainDispatcher);
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
@@ -1130,7 +1135,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// Gone -> Dreaming hosted in lockscreen
collectFlow(mView, mKeyguardTransitionInteractor
- .getGoneToDreamingLockscreenHostedTransition(),
+ .transition(GONE, DREAMING_LOCKSCREEN_HOSTED),
mGoneToDreamingLockscreenHostedTransition, mMainDispatcher);
collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(),
setTransitionAlpha(mNotificationStackScrollLayoutController),
@@ -1138,16 +1143,16 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// Lockscreen -> Dreaming hosted in lockscreen
collectFlow(mView, mKeyguardTransitionInteractor
- .getLockscreenToDreamingLockscreenHostedTransition(),
+ .transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED),
mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher);
// Dreaming hosted in lockscreen -> Lockscreen
collectFlow(mView, mKeyguardTransitionInteractor
- .getDreamingLockscreenHostedToLockscreenTransition(),
+ .transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN),
mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher);
// Occluded->Lockscreen
- collectFlow(mView, mKeyguardTransitionInteractor.getOccludedToLockscreenTransition(),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(OCCLUDED, LOCKSCREEN),
mOccludedToLockscreenTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(),
@@ -1158,7 +1163,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
// Lockscreen->Dreaming
- collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING),
mLockscreenToDreamingTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
@@ -1170,7 +1175,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
// Gone->Dreaming
- collectFlow(mView, mKeyguardTransitionInteractor.getGoneToDreamingTransition(),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(GONE, DREAMING),
mGoneToDreamingTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(),
@@ -1181,7 +1186,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
// Lockscreen->Occluded
- collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToOccludedTransition(),
+ collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, OCCLUDED),
mLockscreenToOccludedTransition, mMainDispatcher);
if (!MigrateClocksToBlueprint.isEnabled()) {
collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 6ac81d226eee..b2952dcbcb39 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -18,6 +18,8 @@ package com.android.systemui.shade;
import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING;
+import static com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
@@ -221,7 +223,7 @@ public class NotificationShadeWindowViewController implements Dumpable {
mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView);
bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container));
- collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
+ collectFlow(mView, keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING),
mLockscreenToDreamingTransition);
collectFlow(
mView,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index d68e28c930f4..0b45c0834245 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -82,7 +82,7 @@ constructor(
override val isShadeTouchable: Flow<Boolean> =
combine(
powerInteractor.isAsleep,
- keyguardTransitionInteractor.isInTransitionToStateWhere { it == KeyguardState.AOD },
+ keyguardTransitionInteractor.isInTransitionToState(KeyguardState.AOD),
keyguardRepository.dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING },
) { isAsleep, goingToSleep, isPulsing ->
when {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index e7b159a2d057..d955349ffede 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -50,6 +50,7 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.Pair;
import android.util.SparseArray;
import android.view.KeyEvent;
@@ -516,7 +517,7 @@ public class CommandQueue extends IStatusBar.Stub implements
/**
* @see IStatusBar#showMediaOutputSwitcher
*/
- default void showMediaOutputSwitcher(String packageName) {}
+ default void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {}
/**
* @see IStatusBar#confirmImmersivePrompt
@@ -1361,7 +1362,7 @@ public class CommandQueue extends IStatusBar.Stub implements
}
}
@Override
- public void showMediaOutputSwitcher(String packageName) {
+ public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) {
int callingUid = Binder.getCallingUid();
if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
throw new SecurityException("Call only allowed from system server.");
@@ -1369,6 +1370,7 @@ public class CommandQueue extends IStatusBar.Stub implements
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = packageName;
+ args.arg2 = userHandle;
mHandler.obtainMessage(MSG_SHOW_MEDIA_OUTPUT_SWITCHER, args).sendToTarget();
}
}
@@ -1939,8 +1941,10 @@ public class CommandQueue extends IStatusBar.Stub implements
case MSG_SHOW_MEDIA_OUTPUT_SWITCHER:
args = (SomeArgs) msg.obj;
String clientPackageName = (String) args.arg1;
+ UserHandle clientUserHandle = (UserHandle) args.arg2;
for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).showMediaOutputSwitcher(clientPackageName);
+ mCallbacks.get(i).showMediaOutputSwitcher(clientPackageName,
+ clientUserHandle);
}
break;
case MSG_CONFIRM_IMMERSIVE_PROMPT:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 815236e0820c..09985f842185 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -195,20 +195,20 @@ public class KeyguardIndicationController {
private boolean mOrganizationOwnedDevice;
// these all assume the device is plugged in (wired/wireless/docked) AND chargingOrFull:
- private boolean mPowerPluggedIn;
- private boolean mPowerPluggedInWired;
- private boolean mPowerPluggedInWireless;
- private boolean mPowerPluggedInDock;
+ protected boolean mPowerPluggedIn;
+ protected boolean mPowerPluggedInWired;
+ protected boolean mPowerPluggedInWireless;
+ protected boolean mPowerPluggedInDock;
private boolean mPowerCharged;
private boolean mBatteryDefender;
private boolean mEnableBatteryDefender;
private boolean mIncompatibleCharger;
- private int mChargingSpeed;
+ protected int mChargingSpeed;
private int mChargingWattage;
private int mBatteryLevel;
private boolean mBatteryPresent = true;
- private long mChargingTimeRemaining;
+ protected long mChargingTimeRemaining;
private Pair<String, BiometricSourceType> mBiometricErrorMessageToShowOnScreenOn;
private final Set<Integer> mCoExFaceAcquisitionMsgIdsToShow;
private final FaceHelpMessageDeferral mFaceAcquiredMessageDeferral;
@@ -1053,20 +1053,24 @@ public class KeyguardIndicationController {
* Assumption: device is charging
*/
protected String computePowerIndication() {
- int chargingId;
if (mBatteryDefender) {
- chargingId = R.string.keyguard_plugged_in_charging_limited;
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
- return mContext.getResources().getString(chargingId, percentage);
+ return mContext.getResources().getString(
+ R.string.keyguard_plugged_in_charging_limited, percentage);
} else if (mPowerPluggedIn && mIncompatibleCharger) {
- chargingId = R.string.keyguard_plugged_in_incompatible_charger;
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
- return mContext.getResources().getString(chargingId, percentage);
+ return mContext.getResources().getString(
+ R.string.keyguard_plugged_in_incompatible_charger, percentage);
} else if (mPowerCharged) {
return mContext.getResources().getString(R.string.keyguard_charged);
}
+ return computePowerChargingStringIndication();
+ }
+
+ protected String computePowerChargingStringIndication() {
final boolean hasChargingTime = mChargingTimeRemaining > 0;
+ int chargingId;
if (mPowerPluggedInWired) {
switch (mChargingSpeed) {
case BatteryStatus.CHARGING_FAST:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
index ed8c05688a66..77660eb7d864 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRepository.kt
@@ -29,9 +29,9 @@ interface HeadsUpRepository {
/**
* True if we are exiting the headsUp pinned mode, and some notifications might still be
- * animating out. This is used to keep the touchable regions in a reasonable state.
+ * animating out. This is used to keep their view container visible.
*/
- val headsUpAnimatingAway: Flow<Boolean>
+ val isHeadsUpAnimatingAway: Flow<Boolean>
/** The heads up row that should be displayed on top. */
val topHeadsUpRow: Flow<HeadsUpRowRepository?>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index d1dd7b55c11f..7f94da3c8c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -60,7 +60,7 @@ class HeadsUpNotificationInteractor @Inject constructor(repository: HeadsUpRepos
}
val isHeadsUpOrAnimatingAway: Flow<Boolean> =
- combine(hasPinnedRows, repository.headsUpAnimatingAway) { hasPinnedRows, animatingAway ->
+ combine(hasPinnedRows, repository.isHeadsUpAnimatingAway) { hasPinnedRows, animatingAway ->
hasPinnedRows || animatingAway
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 5eaccd924344..e980794d23dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -594,7 +594,11 @@ public class StackScrollAlgorithm {
);
if (view instanceof FooterView) {
if (FooterViewRefactor.isEnabled()) {
- if (((FooterView) view).shouldBeHidden()) {
+ // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed
+ // already, so we shouldn't need to use ambientState here. However, currently it
+ // doesn't get updated quickly enough and can cause the footer to flash when
+ // closing the shade. As such, we temporarily also check the ambientState directly.
+ if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) {
viewState.hidden = true;
} else {
final float footerEnd = algorithmState.mCurrentExpandedYPosition
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 13e36d58f6a9..77a0c2e9224c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -207,9 +207,7 @@ constructor(
keyguardTransitionInteractor.finishedKeyguardState.map {
statesForConstrainedNotifications.contains(it)
},
- keyguardTransitionInteractor
- .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
- .onStart { emit(false) }
+ keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f },
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
constrainedNotificationState || transitioningToOrFromLockscreen
}
@@ -241,11 +239,10 @@ constructor(
keyguardTransitionInteractor.finishedKeyguardState.map { state ->
state == GLANCEABLE_HUB
},
- keyguardTransitionInteractor
- .isInTransitionWhere { from, to ->
- from == GLANCEABLE_HUB || to == GLANCEABLE_HUB
- }
- .onStart { emit(false) }
+ or(
+ keyguardTransitionInteractor.isInTransitionToState(GLANCEABLE_HUB),
+ keyguardTransitionInteractor.isInTransitionFromState(GLANCEABLE_HUB),
+ ),
) { isOnGlanceableHub, transitioningToOrFromHub ->
isOnGlanceableHub || transitioningToOrFromHub
}
@@ -290,12 +287,10 @@ constructor(
var aodTransitionIsComplete = true
return combine(
isOnLockscreenWithoutShade,
- keyguardTransitionInteractor
- .isInTransitionWhere(
- fromStatePredicate = { it == LOCKSCREEN },
- toStatePredicate = { it == AOD }
- )
- .onStart { emit(false) },
+ keyguardTransitionInteractor.isInTransition(
+ from = LOCKSCREEN,
+ to = AOD,
+ ),
::Pair
)
.transformWhile { (isOnLockscreenWithoutShade, aodTransitionIsRunning) ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 3f200d578261..0ddf37db6078 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -91,7 +91,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
StateFlowKt.MutableStateFlow(null);
private final MutableStateFlow<Set<HeadsUpRowRepository>> mHeadsUpNotificationRows =
StateFlowKt.MutableStateFlow(new HashSet<>());
- private final MutableStateFlow<Boolean> mHeadsUpGoingAway = StateFlowKt.MutableStateFlow(false);
+ private final MutableStateFlow<Boolean> mHeadsUpAnimatingAway =
+ StateFlowKt.MutableStateFlow(false);
private boolean mReleaseOnExpandFinish;
private boolean mTrackingHeadsUp;
private final HashSet<String> mSwipedOutKeys = new HashSet<>();
@@ -184,7 +185,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
// Public methods:
/**
- * Add a listener to receive callbacks onHeadsUpGoingAway
+ * Add a listener to receive callbacks {@link #setHeadsUpAnimatingAway(boolean)}
*/
@Override
public void addHeadsUpPhoneListener(OnHeadsUpPhoneListenerChange listener) {
@@ -264,7 +265,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
- mHeadsUpGoingAway.setValue(false);
+ mHeadsUpAnimatingAway.setValue(false);
}
}
}
@@ -274,20 +275,15 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
* animating out. This is used to keep the touchable regions in a reasonable state.
*/
@Override
- public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
- if (headsUpGoingAway != mHeadsUpGoingAway.getValue()) {
+ public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ if (headsUpAnimatingAway != mHeadsUpAnimatingAway.getValue()) {
for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
- listener.onHeadsUpGoingAwayStateChanged(headsUpGoingAway);
+ listener.onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway);
}
- mHeadsUpGoingAway.setValue(headsUpGoingAway);
+ mHeadsUpAnimatingAway.setValue(headsUpAnimatingAway);
}
}
- @Override
- public boolean isHeadsUpGoingAway() {
- return mHeadsUpGoingAway.getValue();
- }
-
/**
* Notifies that a remote input textbox in notification gets active or inactive.
*
@@ -504,8 +500,13 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
@Override
@NonNull
- public Flow<Boolean> getHeadsUpAnimatingAway() {
- return mHeadsUpGoingAway;
+ public Flow<Boolean> isHeadsUpAnimatingAway() {
+ return mHeadsUpAnimatingAway;
+ }
+
+ @Override
+ public boolean isHeadsUpAnimatingAwayValue() {
+ return mHeadsUpAnimatingAway.getValue();
}
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index ed1f6ff7e513..87139ac0cada 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -98,11 +98,11 @@ public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener,
// we need to keep the panel open artificially, let's wait until the
//animation
// is finished.
- mHeadsUpManager.setHeadsUpGoingAway(true);
+ mHeadsUpManager.setHeadsUpAnimatingAway(true);
mNsslController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mNotificationShadeWindowController.setHeadsUpShowing(false);
- mHeadsUpManager.setHeadsUpGoingAway(false);
+ mHeadsUpManager.setHeadsUpAnimatingAway(false);
}
mNotificationRemoteInputManager.onPanelCollapsed();
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index c615887d5c25..8e8de46957ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -121,7 +121,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
updateTouchableRegion();
}
});
- mHeadsUpManager.addHeadsUpPhoneListener(this::onHeadsUpGoingAwayStateChanged);
+ mHeadsUpManager.addHeadsUpPhoneListener(this::onHeadsUpAnimatingAwayStateChanged);
mNotificationShadeWindowController = notificationShadeWindowController;
mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
@@ -214,7 +214,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
&& (mNotificationShadeWindowView.getRootWindowInsets() != null)
&& (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null);
boolean shouldObserve = mHeadsUpManager.hasPinnedHeadsUp()
- || mHeadsUpManager.isHeadsUpGoingAway()
+ || mHeadsUpManager.isHeadsUpAnimatingAwayValue()
|| mForceCollapsedUntilLayout
|| hasCutoutInset
|| mNotificationShadeWindowController.getForcePluginOpen();
@@ -288,8 +288,8 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
|| mUnlockedScreenOffAnimationController.isAnimationPlaying();
}
- private void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway) {
- if (!headsUpGoingAway) {
+ private void onHeadsUpAnimatingAwayStateChanged(boolean headsUpAnimatingAway) {
+ if (!headsUpAnimatingAway) {
updateTouchableRegionAfterLayout();
} else {
updateTouchableRegion();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index 52a6d8cf0952..cc87e8a45d13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -19,6 +19,9 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
@@ -77,15 +80,13 @@ constructor(
@Application coroutineScope: CoroutineScope,
) : CollapsedStatusBarViewModel {
override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> =
- keyguardTransitionInteractor.lockscreenToOccludedTransition
- .map {
- it.transitionState == TransitionState.STARTED ||
- it.transitionState == TransitionState.RUNNING
- }
+ keyguardTransitionInteractor
+ .isInTransition(LOCKSCREEN, OCCLUDED)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false)
override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> =
- keyguardTransitionInteractor.lockscreenToDreamingTransition
+ keyguardTransitionInteractor
+ .transition(LOCKSCREEN, DREAMING)
.filter { it.transitionState == TransitionState.STARTED }
.map {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index 52a2e9ccc163..28a2a1f49bf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -73,7 +73,8 @@ interface HeadsUpManager : Dumpable {
/** Returns whether or not the given notification is managed by this manager. */
fun isHeadsUpEntry(key: String): Boolean
- fun isHeadsUpGoingAway(): Boolean
+ /** @see setHeadsUpAnimatingAway */
+ fun isHeadsUpAnimatingAwayValue(): Boolean
/** Returns if the given notification is snoozed or not. */
fun isSnoozed(packageName: String): Boolean
@@ -130,7 +131,7 @@ interface HeadsUpManager : Dumpable {
* Set that we are exiting the headsUp pinned mode, but some notifications might still be
* animating out. This is used to keep the touchable regions in a reasonable state.
*/
- fun setHeadsUpGoingAway(headsUpGoingAway: Boolean)
+ fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean)
/**
* Notifies that a remote input textbox in notification gets active or inactive.
@@ -194,10 +195,10 @@ interface AnimationStateHandler {
interface OnHeadsUpPhoneListenerChange {
/**
* Called when a heads up notification is 'going away' or no longer 'going away'. See
- * [HeadsUpManager.setHeadsUpGoingAway].
+ * [HeadsUpManager.setHeadsUpAnimatingAway].
*/
// TODO(b/325936094) delete this callback, and listen to the flow instead
- fun onHeadsUpGoingAwayStateChanged(headsUpGoingAway: Boolean)
+ fun onHeadsUpAnimatingAwayStateChanged(headsUpAnimatingAway: Boolean)
}
/* No op impl of HeadsUpManager. */
@@ -215,7 +216,7 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager {
override fun getTopEntry() = null
override fun hasPinnedHeadsUp() = false
override fun isHeadsUpEntry(key: String) = false
- override fun isHeadsUpGoingAway() = false
+ override fun isHeadsUpAnimatingAwayValue() = false
override fun isSnoozed(packageName: String) = false
override fun isSticky(key: String?) = false
override fun isTrackingHeadsUp() = false
@@ -228,7 +229,7 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager {
override fun setAnimationStateHandler(handler: AnimationStateHandler) {}
override fun setExpanded(entry: NotificationEntry, expanded: Boolean) {}
override fun setGutsShown(entry: NotificationEntry, gutsShown: Boolean) {}
- override fun setHeadsUpGoingAway(headsUpGoingAway: Boolean) {}
+ override fun setHeadsUpAnimatingAway(headsUpAnimatingAway: Boolean) {}
override fun setRemoteInputActive(entry: NotificationEntry, remoteInputActive: Boolean) {}
override fun setTrackingHeadsUp(tracking: Boolean) {}
override fun setUser(user: Int) {}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt
new file mode 100644
index 000000000000..9b84090d72cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/UiEventLoggerStartableModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.volume.domain.startable.AudioModeLoggerStartable
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface UiEventLoggerStartableModule {
+
+ @Binds
+ @IntoSet
+ fun bindAudioModeLoggerStartable(
+ audioModeLoggerStartable: AudioModeLoggerStartable,
+ ): VolumePanelStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt
new file mode 100644
index 000000000000..12447577e945
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/startable/AudioModeLoggerStartable.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.domain.startable
+
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+
+/** Logger for audio mode */
+@VolumePanelScope
+class AudioModeLoggerStartable
+@Inject
+constructor(
+ @VolumePanelScope private val scope: CoroutineScope,
+ private val uiEventLogger: UiEventLogger,
+ private val audioModeInteractor: AudioModeInteractor,
+) : VolumePanelStartable {
+
+ override fun start() {
+ scope.launch {
+ audioModeInteractor.isOngoingCall.distinctUntilChanged().collect { ongoingCall ->
+ uiEventLogger.log(
+ if (ongoingCall) VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING
+ else VolumePanelUiEvent.VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
index 04d7b1fa6532..3ca9cdfe285c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModel.kt
@@ -18,8 +18,10 @@ package com.android.systemui.volume.panel.component.bottombar.ui.viewmodel
import android.content.Intent
import android.provider.Settings
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -29,6 +31,7 @@ class BottomBarViewModel
constructor(
private val activityStarter: ActivityStarter,
private val volumePanelViewModel: VolumePanelViewModel,
+ private val uiEventLogger: UiEventLogger,
) {
fun onDoneClicked() {
@@ -36,6 +39,7 @@ constructor(
}
fun onSettingsClicked() {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SOUND_SETTINGS_CLICKED)
activityStarter.startActivityDismissingKeyguard(
/* intent = */ Intent(Settings.ACTION_SOUND_SETTINGS),
/* onlyProvisioned = */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
index aab825fb9f5e..85da1d0efe3a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
@@ -16,18 +16,36 @@
package com.android.systemui.volume.panel.component.captioning.domain
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.shareIn
@VolumePanelScope
class CaptioningAvailabilityCriteria
@Inject
-constructor(private val captioningInteractor: CaptioningInteractor) :
- ComponentAvailabilityCriteria {
+constructor(
+ captioningInteractor: CaptioningInteractor,
+ @VolumePanelScope private val scope: CoroutineScope,
+ private val uiEventLogger: UiEventLogger,
+) : ComponentAvailabilityCriteria {
- override fun isAvailable(): Flow<Boolean> =
+ private val availability =
captioningInteractor.isSystemAudioCaptioningUiEnabled
+ .onEach { visible ->
+ uiEventLogger.log(
+ if (visible) VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_SHOWN
+ else VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE
+ )
+ }
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+
+ override fun isAvailable(): Flow<Boolean> = availability
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
index 92f8f221d918..01421f86311f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/ui/viewmodel/CaptioningViewModel.kt
@@ -17,11 +17,13 @@
package com.android.systemui.volume.panel.component.captioning.ui.viewmodel
import android.content.Context
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.view.accessibility.domain.interactor.CaptioningInteractor
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -38,6 +40,7 @@ constructor(
private val context: Context,
private val captioningInteractor: CaptioningInteractor,
@VolumePanelScope private val coroutineScope: CoroutineScope,
+ private val uiEventLogger: UiEventLogger,
) {
val buttonViewModel: StateFlow<ToggleButtonViewModel?> =
@@ -57,6 +60,13 @@ constructor(
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) {
+ uiEventLogger.logWithPosition(
+ VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_CLICKED,
+ 0,
+ null,
+ if (enabled) VolumePanelUiEvent.LIVE_CAPTION_TOGGLE_ENABLED
+ else VolumePanelUiEvent.LIVE_CAPTION_TOGGLE_DISABLED
+ )
coroutineScope.launch { captioningInteractor.setIsSystemAudioCaptioningEnabled(enabled) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index fc9602e6017f..6b237f8e329b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
import android.content.Context
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
@@ -26,6 +27,7 @@ import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -48,6 +50,7 @@ constructor(
private val actionsInteractor: MediaOutputActionsInteractor,
private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
interactor: MediaOutputInteractor,
+ private val uiEventLogger: UiEventLogger,
) {
private val sessionWithPlayback: StateFlow<SessionWithPlayback?> =
@@ -126,6 +129,7 @@ constructor(
)
fun onBarClick(expandable: Expandable) {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index f022039e9cde..4ecdd46163f9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
import android.content.Context
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
@@ -29,6 +30,7 @@ import com.android.systemui.volume.panel.component.spatial.domain.interactor.Spa
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -46,6 +48,7 @@ constructor(
@VolumePanelScope private val scope: CoroutineScope,
availabilityCriteria: SpatialAudioAvailabilityCriteria,
private val interactor: SpatialAudioComponentInteractor,
+ private val uiEventLogger: UiEventLogger,
) {
val spatialAudioButton: StateFlow<ButtonViewModel?> =
@@ -101,6 +104,19 @@ constructor(
.stateIn(scope, SharingStarted.Eagerly, emptyList())
fun setEnabled(model: SpatialAudioEnabledModel) {
+ uiEventLogger.logWithPosition(
+ VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_TOGGLE_CLICKED,
+ 0,
+ null,
+ when (model) {
+ SpatialAudioEnabledModel.Disabled -> 0
+ SpatialAudioEnabledModel.SpatialAudioEnabled -> 1
+ SpatialAudioEnabledModel.HeadTrackingEnabled -> 2
+ else -> {
+ -1
+ }
+ }
+ )
scope.launch { interactor.setEnabled(model) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 1ae1ebb99f8b..c8cd6fdbea70 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,12 +18,14 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.Context
import android.media.AudioManager
+import com.android.internal.logging.UiEventLogger
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.AudioStreamModel
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -43,6 +45,7 @@ constructor(
@Assisted private val coroutineScope: CoroutineScope,
private val context: Context,
private val audioVolumeInteractor: AudioVolumeInteractor,
+ private val uiEventLogger: UiEventLogger,
) : SliderViewModel {
private val audioStream = audioStreamWrapper.audioStream
@@ -69,6 +72,19 @@ constructor(
AudioStream(AudioManager.STREAM_ALARM) to R.string.stream_alarm_unavailable,
AudioStream(AudioManager.STREAM_MUSIC) to R.string.stream_media_unavailable,
)
+ private val uiEventByStream =
+ mapOf(
+ AudioStream(AudioManager.STREAM_MUSIC) to
+ VolumePanelUiEvent.VOLUME_PANEL_MUSIC_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_VOICE_CALL) to
+ VolumePanelUiEvent.VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_RING) to
+ VolumePanelUiEvent.VOLUME_PANEL_RING_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_NOTIFICATION) to
+ VolumePanelUiEvent.VOLUME_PANEL_NOTIFICATION_SLIDER_TOUCHED,
+ AudioStream(AudioManager.STREAM_ALARM) to
+ VolumePanelUiEvent.VOLUME_PANEL_ALARM_SLIDER_TOUCHED,
+ )
override val slider: StateFlow<SliderState> =
combine(
@@ -88,6 +104,10 @@ constructor(
}
}
+ override fun onValueChangeFinished() {
+ uiEventByStream[audioStream]?.let { uiEventLogger.log(it) }
+ }
+
override fun toggleMuted(state: SliderState) {
val audioViewModel = state as? State
audioViewModel ?: return
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 3689303ab28d..956ab66ac0c3 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -54,6 +54,8 @@ constructor(
}
}
+ override fun onValueChangeFinished() {}
+
override fun toggleMuted(state: SliderState) {
// do nothing because this action isn't supported for Cast sliders.
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 74aee559194b..7ded8c5c9fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -25,5 +25,7 @@ interface SliderViewModel {
fun onValueChanged(state: SliderState, newValue: Float)
+ fun onValueChangeFinished()
+
fun toggleMuted(state: SliderState)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
index d1d539003f93..f889ed6e06be 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/DefaultMultibindsModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.dagger
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Module
@@ -31,4 +32,6 @@ interface DefaultMultibindsModule {
@Multibinds fun criteriaMap(): Map<VolumePanelComponentKey, ComponentAvailabilityCriteria>
@Multibinds fun components(): Map<VolumePanelComponentKey, VolumePanelUiComponent>
+
+ @Multibinds fun startables(): Set<VolumePanelStartable>
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index d868c33d0887..ec64f3d93012 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.dagger
+import com.android.systemui.volume.dagger.UiEventLoggerStartableModule
import com.android.systemui.volume.panel.component.anc.AncModule
import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.component.captioning.CaptioningModule
@@ -25,6 +26,7 @@ import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.domain.DomainModule
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.UiModule
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
@@ -47,6 +49,7 @@ import kotlinx.coroutines.CoroutineScope
DefaultMultibindsModule::class,
DomainModule::class,
UiModule::class,
+ UiEventLoggerStartableModule::class,
// Components modules
BottomBarModule::class,
AncModule::class,
@@ -66,6 +69,8 @@ interface VolumePanelComponent {
fun componentsLayoutManager(): ComponentsLayoutManager
+ fun volumePanelStartables(): Set<VolumePanelStartable>
+
@Subcomponent.Factory
interface Factory : VolumePanelComponentFactory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt
new file mode 100644
index 000000000000..9c39f5e75f88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/VolumePanelStartable.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.domain
+
+/** Code that needs to be run when Volume Panel is started.. */
+interface VolumePanelStartable {
+ fun start()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
new file mode 100644
index 000000000000..8b8714fcca8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/VolumePanelUiEvent.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.ui
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/** UI events for Volume Panel. */
+enum class VolumePanelUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The volume panel is shown") VOLUME_PANEL_SHOWN(1634),
+ @UiEvent(doc = "The volume panel is gone") VOLUME_PANEL_GONE(1635),
+ @UiEvent(doc = "Media output is clicked") VOLUME_PANEL_MEDIA_OUTPUT_CLICKED(1636),
+ @UiEvent(doc = "Audio mode changed to normal") VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_NORMAL(1680),
+ @UiEvent(doc = "Audio mode changed to calling") VOLUME_PANEL_AUDIO_MODE_CHANGE_TO_CALLING(1681),
+ @UiEvent(doc = "Sound settings is clicked") VOLUME_PANEL_SOUND_SETTINGS_CLICKED(1638),
+ @UiEvent(doc = "The music volume slider is touched") VOLUME_PANEL_MUSIC_SLIDER_TOUCHED(1639),
+ @UiEvent(doc = "The voice call volume slider is touched")
+ VOLUME_PANEL_VOICE_CALL_SLIDER_TOUCHED(1640),
+ @UiEvent(doc = "The ring volume slider is touched") VOLUME_PANEL_RING_SLIDER_TOUCHED(1641),
+ @UiEvent(doc = "The notification volume slider is touched")
+ VOLUME_PANEL_NOTIFICATION_SLIDER_TOUCHED(1642),
+ @UiEvent(doc = "The alarm volume slider is touched") VOLUME_PANEL_ALARM_SLIDER_TOUCHED(1643),
+ @UiEvent(doc = "Live caption toggle is shown") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_SHOWN(1644),
+ @UiEvent(doc = "Live caption toggle is gone") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE(1645),
+ @UiEvent(doc = "Live caption toggle is clicked") VOLUME_PANEL_LIVE_CAPTION_TOGGLE_CLICKED(1646),
+ @UiEvent(doc = "Spatial audio button is shown") VOLUME_PANEL_SPATIAL_AUDIO_BUTTON_SHOWN(1647),
+ @UiEvent(doc = "Spatial audio button is gone") VOLUME_PANEL_SPATIAL_AUDIO_BUTTON_GONE(1648),
+ @UiEvent(doc = "Spatial audio popup is shown") VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN(1649),
+ @UiEvent(doc = "Spatial audio toggle is clicked")
+ VOLUME_PANEL_SPATIAL_AUDIO_TOGGLE_CLICKED(1650),
+ @UiEvent(doc = "ANC button is shown") VOLUME_PANEL_ANC_BUTTON_SHOWN(1651),
+ @UiEvent(doc = "ANC button is gone") VOLUME_PANEL_ANC_BUTTON_GONE(1652),
+ @UiEvent(doc = "ANC popup is shown") VOLUME_PANEL_ANC_POPUP_SHOWN(1653),
+ @UiEvent(doc = "ANC toggle is clicked") VOLUME_PANEL_ANC_TOGGLE_CLICKED(1654);
+
+ override fun getId() = metricId
+
+ companion object {
+ const val LIVE_CAPTION_TOGGLE_DISABLED = 0
+ const val LIVE_CAPTION_TOGGLE_ENABLED = 1
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
index c728fefa77e6..ccb91ac79b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/activity/VolumePanelActivity.kt
@@ -21,8 +21,10 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag
+import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import com.android.systemui.volume.panel.ui.composable.VolumePanelRoot
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
import javax.inject.Inject
@@ -34,6 +36,7 @@ constructor(
private val volumePanelViewModelFactory: Provider<VolumePanelViewModel.Factory>,
private val volumePanelFlag: VolumePanelFlag,
private val configurationController: ConfigurationController,
+ private val uiEventLogger: UiEventLogger,
) : ComponentActivity() {
private val viewModel: VolumePanelViewModel by
@@ -43,8 +46,16 @@ constructor(
enableEdgeToEdge()
super.onCreate(savedInstanceState)
volumePanelFlag.assertNewVolumePanel()
-
- setContent { VolumePanelRoot(viewModel = viewModel, onDismiss = ::finish) }
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_SHOWN)
+ setContent {
+ VolumePanelRoot(
+ viewModel = viewModel,
+ onDismiss = {
+ uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_GONE)
+ finish()
+ }
+ )
+ }
}
override fun onContentChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
index 5ae827ff4e3d..1de4fd1f9593 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModel.kt
@@ -26,6 +26,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.onConfigChanged
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
import com.android.systemui.volume.panel.ui.layout.ComponentsLayout
@@ -109,6 +110,10 @@ class VolumePanelViewModel(
replay = 1,
)
+ init {
+ volumePanelComponent.volumePanelStartables().onEach(VolumePanelStartable::start)
+ }
+
fun dismissPanel() {
mutablePanelVisibility.update { false }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index f32d5b8838b7..e72027a921b7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -319,9 +319,19 @@ class ClockEventControllerTest : SysuiTestCase() {
fun listenForDozeAmountTransition_updatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
- whenever(keyguardTransitionInteractor.lockscreenToAodTransition)
+ whenever(
+ keyguardTransitionInteractor.transition(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD
+ )
+ )
.thenReturn(transitionStep)
- whenever(keyguardTransitionInteractor.aodToLockscreenTransition)
+ whenever(
+ keyguardTransitionInteractor.transition(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN
+ )
+ )
.thenReturn(transitionStep)
val job = underTest.listenForDozeAmountTransition(this)
@@ -361,6 +371,27 @@ class ClockEventControllerTest : SysuiTestCase() {
}
@Test
+ fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToLockscreenTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, times(2)).doze(0f)
+
+ job.cancel()
+ }
+
+ @Test
fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() =
runBlocking(IMMEDIATE) {
val transitionStep = MutableStateFlow(TransitionStep())
@@ -378,6 +409,27 @@ class ClockEventControllerTest : SysuiTestCase() {
verify(animations, never()).doze(1f)
+ job.cancel()
+ }
+
+ @Test
+ fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() =
+ runBlocking(IMMEDIATE) {
+ val transitionStep = MutableStateFlow(TransitionStep())
+ whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN))
+ .thenReturn(transitionStep)
+
+ val job = underTest.listenForAnyStateToLockscreenTransition(this)
+ transitionStep.value =
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED,
+ )
+ yield()
+
+ verify(animations, never()).doze(0f)
+
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index dac88a340cb1..e06134bdf982 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -119,6 +119,7 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromOffState() =
testScope.runTest {
underTest.start()
+ runCurrent()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
@@ -160,6 +161,7 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
testScope.runTest {
underTest.start()
+ runCurrent()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
@@ -207,6 +209,7 @@ class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() =
testScope.runTest {
underTest.start()
+ runCurrent()
powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_LID)
faceWakeUpTriggersConfig.setTriggerFaceAuthOnWakeUpFrom(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 2b51863117e9..b0aace6f650e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -15,6 +15,8 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
@@ -22,7 +24,6 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -42,8 +43,7 @@ import org.mockito.MockitoAnnotations
class ResourceTrimmerTest : SysuiTestCase() {
val kosmos = testKosmos()
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private val testScope = kosmos.testScope
private val keyguardRepository = kosmos.fakeKeyguardRepository
private val featureFlags = kosmos.fakeFeatureFlagsClassic
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
@@ -74,7 +74,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
kosmos.keyguardTransitionInteractor,
globalWindowManager,
testScope.backgroundScope,
- testDispatcher,
+ kosmos.testDispatcher,
featureFlags
)
resourceTrimmer.start()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6d605a564022..b1a8dd1d3fdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -281,6 +281,14 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
transitionRepository.sendTransitionStep(
TransitionStep(
+ transitionState = TransitionState.CANCELED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
index 143c4dacb6be..1396b20a800d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
@@ -57,11 +57,18 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() {
stepFromAlternateBouncer(0f, TransitionState.STARTED),
stepFromAlternateBouncer(.4f),
stepFromAlternateBouncer(.6f),
- stepFromAlternateBouncer(1f),
),
testScope,
)
assertThat(alternateBouncerWindowRequired).isTrue()
+
+ transitionRepository.sendTransitionSteps(
+ listOf(
+ stepFromAlternateBouncer(1.0f, TransitionState.FINISHED),
+ ),
+ testScope,
+ )
+ assertThat(alternateBouncerWindowRequired).isFalse()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index b70cc30eb3e1..fe8fdc042ae4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -29,7 +29,9 @@ import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_RESUME
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
+import com.android.systemui.media.controls.shared.model.SmartspaceMediaLoadingModel
import com.android.systemui.media.controls.ui.controller.MediaPlayerData
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -48,12 +50,10 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Mock
import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -76,7 +76,6 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
@TestableLooper.RunWithLooper
class MediaDataFilterImplTest : SysuiTestCase() {
- @Mock private lateinit var listener: MediaDataFilterImpl.Listener
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var broadcastSender: BroadcastSender
@Mock private lateinit var mediaDataManager: MediaDataManager
@@ -89,7 +88,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Mock private lateinit var cardAction: SmartspaceAction
private lateinit var mediaDataFilter: MediaDataFilterImpl
- private lateinit var mediaFilterRepository: MediaFilterRepository
+ private lateinit var repository: MediaFilterRepository
private lateinit var testScope: TestScope
private lateinit var dataMain: MediaData
private lateinit var dataGuest: MediaData
@@ -102,7 +101,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
MediaPlayerData.clear()
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
testScope = TestScope()
- mediaFilterRepository = MediaFilterRepository()
+ repository = MediaFilterRepository()
mediaDataFilter =
MediaDataFilterImpl(
context,
@@ -113,10 +112,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
clock,
logger,
mediaFlags,
- mediaFilterRepository,
+ repository,
)
mediaDataFilter.mediaDataManager = mediaDataManager
- mediaDataFilter.addListener(listener)
// Start all tests as main user
setUser(USER_MAIN)
@@ -162,91 +160,114 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnDataLoadedForCurrentUser_callsListener() {
- // GIVEN a media for main user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ fun onDataLoadedForCurrentUser_updatesLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaDataLoadingModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // THEN we should tell the listener
- verify(listener).onMediaDataLoaded(eq(dataMain.instanceId), eq(true), eq(0), eq(false))
- }
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
+ }
@Test
- fun testOnDataLoadedForGuest_doesNotCallListener() {
- // GIVEN a media for guest user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ fun onDataLoadedForGuest_doesNotUpdateLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
- }
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+
+ assertThat(mediaDataLoadedStates).isNotEqualTo(mediaLoadedStatesModel)
+ }
@Test
- fun testOnRemovedForCurrent_callsListener() {
- // GIVEN a media was removed for main user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ fun onRemovedForCurrent_updatesLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaLoadedStatesModel =
+ mutableListOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // THEN we should tell the listener
- verify(listener).onMediaDataRemoved(eq(dataMain.instanceId))
- }
+ // GIVEN a media was removed for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
+ mediaLoadedStatesModel.remove(MediaDataLoadingModel.Loaded(dataMain.instanceId))
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ }
@Test
- fun testOnRemovedForGuest_doesNotCallListener() {
- // GIVEN a media was removed for guest user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
- mediaDataFilter.onMediaDataRemoved(KEY)
+ fun onRemovedForGuest_doesNotUpdateLoadedStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
- // THEN we should NOT tell the listener
- verify(listener, never()).onMediaDataRemoved(eq(dataGuest.instanceId))
- }
+ // GIVEN a media was removed for guest user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ assertThat(mediaDataLoadedStates).isEmpty()
+ }
@Test
- fun testOnUserSwitched_removesOldUserControls() {
- // GIVEN that we have a media loaded for main user
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ fun onUserSwitched_removesOldUserControls() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // and we switch to guest user
- setUser(USER_GUEST)
+ // GIVEN that we have a media loaded for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- // THEN we should remove the main user's media
- verify(listener).onMediaDataRemoved(eq(dataMain.instanceId))
- }
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
+ // and we switch to guest user
+ setUser(USER_GUEST)
+
+ // THEN we should remove the main user's media
+ assertThat(mediaDataLoadedStates).isEmpty()
+ }
@Test
- fun testOnUserSwitched_addsNewUserControls() {
- // GIVEN that we had some media for both users
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
- reset(listener)
+ fun onUserSwitched_addsNewUserControls() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val guestLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataGuest.instanceId))
+ val mainLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // and we switch to guest user
- setUser(USER_GUEST)
+ // GIVEN that we had some media for both users
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataGuest)
- // THEN we should add back the guest user media
- verify(listener).onMediaDataLoaded(eq(dataGuest.instanceId), eq(true), eq(0), eq(false))
+ // and we switch to guest user
+ setUser(USER_GUEST)
- // but not the main user's
- verify(listener, never())
- .onMediaDataLoaded(eq(dataMain.instanceId), anyBoolean(), anyInt(), anyBoolean())
- }
+ assertThat(mediaDataLoadedStates).isEqualTo(guestLoadedStatesModel)
+ assertThat(mediaDataLoadedStates).isNotEqualTo(mainLoadedStatesModel)
+ }
@Test
- fun testOnProfileChanged_profileUnavailable_loadControls() {
- // GIVEN that we had some media for both profiles
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
- mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile)
- reset(listener)
+ fun onProfileChanged_profileUnavailable_updateStates() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
- // and we change profile status
- setPrivateProfileUnavailable()
+ // GIVEN that we had some media for both profiles
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onMediaDataLoaded(KEY_ALT, null, dataPrivateProfile)
- // THEN we should add the private profile media
- verify(listener).onMediaDataRemoved(eq(dataPrivateProfile.instanceId))
- }
+ // and we change profile status
+ setPrivateProfileUnavailable()
+
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
+ // THEN we should remove the private profile media
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+ }
@Test
fun hasAnyMedia_mediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
assertThat(hasAnyMedia(selectedUserEntries)).isTrue()
@@ -255,7 +276,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasAnyMedia_recommendationSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(hasAnyMedia(selectedUserEntries)).isFalse()
@@ -264,8 +285,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasAnyMediaOrRecommendation_mediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
@@ -275,8 +296,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasAnyMediaOrRecommendation_recommendationSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
@@ -286,7 +307,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMedia_inactiveMediaSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
val data = dataMain.copy(active = false)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -297,7 +318,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMedia_activeMediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
val data = dataMain.copy(active = true)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -307,9 +328,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_inactiveMediaSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
val data = dataMain.copy(active = false)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -326,9 +347,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_activeMediaSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
val data = dataMain.copy(active = true)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
@@ -345,9 +366,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_inactiveRecommendationSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -364,9 +385,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_invalidRecommendationSet_returnsFalse() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
whenever(smartspaceData.isValid()).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -383,9 +404,9 @@ class MediaDataFilterImplTest : SysuiTestCase() {
@Test
fun hasActiveMediaOrRecommendation_activeAndValidRecommendationSet_returnsTrue() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
whenever(smartspaceData.isActive).thenReturn(true)
whenever(smartspaceData.isValid()).thenReturn(true)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
@@ -401,10 +422,10 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testHasAnyMediaOrRecommendation_onlyCurrentUser() =
+ fun hasAnyMediaOrRecommendation_onlyCurrentUser() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData))
.isFalse()
@@ -415,11 +436,11 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testHasActiveMediaOrRecommendation_onlyCurrentUser() =
+ fun hasActiveMediaOrRecommendation_onlyCurrentUser() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -443,10 +464,10 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnNotificationRemoved_doesNotHaveMedia() =
+ fun onNotificationRemoved_doesNotHaveMedia() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
mediaDataFilter.onMediaDataRemoved(KEY)
@@ -456,7 +477,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSwipeToDismiss_setsTimedOut() {
+ fun onSwipeToDismiss_setsTimedOut() {
mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
mediaDataFilter.onSwipeToDismiss()
@@ -464,15 +485,19 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() =
+ fun onSmartspaceMediaDataLoaded_noMedia_activeValidRec_prioritizesSmartspace() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(true))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -487,18 +512,19 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() =
+ fun onSmartspaceMediaDataLoaded_noMedia_inactiveRec_showsNothing() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -513,17 +539,21 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() =
+ fun onSmartspaceMediaDataLoaded_noRecentMedia_activeValidRec_prioritizesSmartspace() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel =
+ SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true)
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataOld)
clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(true))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -538,11 +568,13 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() =
+ fun onSmartspaceMediaDataLoaded_noRecentMedia_inactiveRec_showsNothing() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
whenever(smartspaceData.isActive).thenReturn(false)
val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
@@ -550,7 +582,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -565,27 +597,29 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() =
+ fun onSmartspaceMediaDataLoaded_hasRecentMedia_inactiveRec_showsNothing() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
whenever(smartspaceData.isActive).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
- reset(listener)
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
// AND we get a smartspace signal
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as not active instead
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ // THEN we should treat the media as not active instead
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -600,27 +634,28 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() =
+ fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeInvalidRec_usesMedia() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
whenever(smartspaceData.isValid()).thenReturn(false)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ // THEN we should treat the media as active instead
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -630,31 +665,35 @@ class MediaDataFilterImplTest : SysuiTestCase() {
)
.isTrue()
// Smartspace update shouldn't be propagated for the empty rec list.
- verify(listener, never()).onSmartspaceMediaDataLoaded(any(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(SmartspaceMediaLoadingModel.Unknown)
verify(logger, never()).logRecommendationAdded(any(), any())
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@Test
- fun testOnSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() =
+ fun onSmartspaceMediaDataLoaded_hasRecentMedia_activeValidRec_usesBoth() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ val mediaDataLoadingModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
// AND we get a smartspace signal
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ // THEN we should treat the media as active instead
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaDataLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -664,22 +703,25 @@ class MediaDataFilterImplTest : SysuiTestCase() {
)
.isTrue()
// Smartspace update should also be propagated but not prioritized.
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID)
verify(logger).logRecommendationActivated(eq(APP_UID), eq(PACKAGE), eq(INSTANCE_ID))
}
@Test
- fun testOnSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspace() =
+ fun onSmartspaceMediaDataRemoved_usedSmartspace_clearsSmartspace() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(SMARTSPACE_KEY)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
- verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -692,26 +734,28 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() =
+ fun onSmartspaceMediaDataRemoved_usedMediaAndSmartspace_clearsBoth() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Removed(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
runCurrent()
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
- verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY)
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -724,17 +768,20 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceLoaded_persistentEnabled_isInactive_notifiesListeners() =
+ fun onSmartspaceLoaded_persistentEnabled_isInactive() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
whenever(smartspaceData.isActive).thenReturn(false)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -748,11 +795,16 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() =
+ fun onSmartspaceLoaded_persistentEnabled_inactive_hasRecentMedia_staysInactive() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
whenever(smartspaceData.isActive).thenReturn(false)
@@ -760,16 +812,14 @@ class MediaDataFilterImplTest : SysuiTestCase() {
// If there is media that was recently played but inactive
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
- reset(listener)
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
// And an inactive recommendation is loaded
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
// Smartspace is loaded but the media stays inactive
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
- verify(listener, never()).onMediaDataLoaded(any(), anyBoolean(), anyInt(), anyBoolean())
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -783,7 +833,7 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testOnSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
+ fun onSwipeToDismiss_persistentEnabled_recommendationSetInactive() {
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(true)
val data =
@@ -802,16 +852,21 @@ class MediaDataFilterImplTest : SysuiTestCase() {
}
@Test
- fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() =
+ fun smartspaceLoaded_shouldTriggerResume_doesTrigger() =
testScope.runTest {
- val selectedUserEntries by collectLastValue(mediaFilterRepository.selectedUserEntries)
- val smartspaceMediaData by collectLastValue(mediaFilterRepository.smartspaceMediaData)
- val reactivatedKey by collectLastValue(mediaFilterRepository.reactivatedId)
+ val selectedUserEntries by collectLastValue(repository.selectedUserEntries)
+ val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData)
+ val reactivatedKey by collectLastValue(repository.reactivatedId)
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
// WHEN we have media that was recently played, but not currently active
val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
// AND we get a smartspace signal with extra to trigger resume
runCurrent()
@@ -819,10 +874,8 @@ class MediaDataFilterImplTest : SysuiTestCase() {
whenever(cardAction.extras).thenReturn(extras)
mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
- // THEN we should tell listeners to treat the media as active instead
- val dataCurrentAndActive = dataCurrent.copy(active = true)
- verify(listener)
- .onMediaDataLoaded(eq(dataCurrentAndActive.instanceId), eq(true), eq(100), eq(true))
+ // THEN we should treat the media as active instead
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
assertThat(
hasActiveMediaOrRecommendation(
selectedUserEntries,
@@ -831,27 +884,33 @@ class MediaDataFilterImplTest : SysuiTestCase() {
)
)
.isTrue()
- // And send the smartspace data, but not prioritized
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
+ // And update the smartspace data state, but not prioritized
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
}
@Test
- fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() {
- // WHEN we have media that was recently played, but not currently active
- val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
- mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- verify(listener).onMediaDataLoaded(eq(dataCurrent.instanceId), eq(true), eq(0), eq(false))
+ fun smartspaceLoaded_notShouldTriggerResume_doesNotTrigger() =
+ testScope.runTest {
+ val mediaDataLoadedStates by collectLastValue(repository.mediaDataLoadedStates)
+ val recommendationsLoadingState by
+ collectLastValue(repository.recommendationsLoadingState)
+ val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY)
+ val mediaLoadedStatesModel = listOf(MediaDataLoadingModel.Loaded(dataMain.instanceId))
- // AND we get a smartspace signal with extra to not trigger resume
- val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
- whenever(cardAction.extras).thenReturn(extras)
- mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+ // WHEN we have media that was recently played, but not currently active
+ val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime())
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent)
- // THEN listeners are not updated to show media
- verify(listener, never()).onMediaDataLoaded(any(), eq(true), eq(100), eq(true))
- // But the smartspace update is still propagated
- verify(listener).onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(false))
- }
+ assertThat(mediaDataLoadedStates).isEqualTo(mediaLoadedStatesModel)
+
+ // AND we get a smartspace signal with extra to not trigger resume
+ val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) }
+ whenever(cardAction.extras).thenReturn(extras)
+ mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData)
+
+ // But the smartspace update is still propagated
+ assertThat(recommendationsLoadingState).isEqualTo(recommendationsLoadingModel)
+ }
private fun hasActiveMediaOrRecommendation(
entries: Map<InstanceId, MediaData>?,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index e7b29d826a0c..0a8e470f8a7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -410,6 +410,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mock(DeviceEntryUdfpsInteractor.class);
when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false));
+ when(mKeyguardTransitionInteractor.isInTransitionToState(any())).thenReturn(emptyFlow());
+
mShadeInteractor = new ShadeInteractorImpl(
mTestScope.getBackgroundScope(),
mKosmos.getDeviceProvisioningInteractor(),
@@ -539,7 +541,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
}).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class));
// Dreaming->Lockscreen
- when(mKeyguardTransitionInteractor.getDreamingToLockscreenTransition())
+ when(mKeyguardTransitionInteractor.transition(any(), any()))
.thenReturn(emptyFlow());
when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
@@ -547,46 +549,28 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
.thenReturn(emptyFlow());
// Occluded->Lockscreen
- when(mKeyguardTransitionInteractor.getOccludedToLockscreenTransition())
- .thenReturn(emptyFlow());
when(mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
when(mOccludedToLockscreenTransitionViewModel.getLockscreenTranslationY())
.thenReturn(emptyFlow());
// Lockscreen->Dreaming
- when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition())
- .thenReturn(emptyFlow());
when(mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
when(mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(anyInt()))
.thenReturn(emptyFlow());
// Gone->Dreaming
- when(mKeyguardTransitionInteractor.getGoneToDreamingTransition())
- .thenReturn(emptyFlow());
when(mGoneToDreamingTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
when(mGoneToDreamingTransitionViewModel.lockscreenTranslationY(anyInt()))
.thenReturn(emptyFlow());
// Gone->Dreaming lockscreen hosted
- when(mKeyguardTransitionInteractor.getGoneToDreamingLockscreenHostedTransition())
- .thenReturn(emptyFlow());
when(mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
- // Dreaming lockscreen hosted->Lockscreen
- when(mKeyguardTransitionInteractor.getDreamingLockscreenHostedToLockscreenTransition())
- .thenReturn(emptyFlow());
-
- // Lockscreen->Dreaming lockscreen hosted
- when(mKeyguardTransitionInteractor.getLockscreenToDreamingLockscreenHostedTransition())
- .thenReturn(emptyFlow());
-
// Lockscreen->Occluded
- when(mKeyguardTransitionInteractor.getLockscreenToOccludedTransition())
- .thenReturn(emptyFlow());
when(mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha())
.thenReturn(emptyFlow());
when(mLockscreenToOccludedTransitionViewModel.getLockscreenTranslationY())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 2c0a15dd4e5a..b04503b8e031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -43,6 +43,8 @@ import com.android.systemui.flags.Flags.TRACKPAD_GESTURE_FEATURES
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
@@ -160,7 +162,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
.thenReturn(keyguardBouncerComponent)
whenever(keyguardBouncerComponent.securityContainerController)
.thenReturn(keyguardSecurityContainerController)
- whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
+ whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING))
.thenReturn(emptyFlow<TransitionStep>())
featureFlagsClassic = FakeFeatureFlagsClassic()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 98a815cabe83..ba8eb6f4ba36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -36,6 +36,8 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
@@ -149,7 +151,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
whenever(statusBarStateController.isDozing).thenReturn(false)
mDependency.injectTestDependency(ShadeController::class.java, shadeController)
whenever(dockManager.isDocked).thenReturn(false)
- whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
+ whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING))
.thenReturn(emptyFlow())
val featureFlags = FakeFeatureFlags()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 4eb7daa1eac7..894e02e80997 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -188,6 +188,9 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
testComponent.runTest {
+ val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
+ assertThat(animationsEnabled).isTrue()
+
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -201,8 +204,6 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
)
)
whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
- val animationsEnabled by collectLastValue(underTest.areContainerChangesAnimated)
- runCurrent()
assertThat(animationsEnabled).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 35b84939b05d..78b76151e7e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -195,6 +195,9 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
@Test
fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() =
testComponent.runTest {
+ val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+ assertThat(animationsEnabled).isTrue()
+
powerRepository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_SLEEP,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -208,7 +211,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() {
)
)
whenever(dozeParams.shouldControlScreenOff()).thenReturn(true)
- val animationsEnabled by collectLastValue(underTest.animationsEnabled)
+
runCurrent()
assertThat(animationsEnabled).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 06a4d0820386..01492f629fe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -398,7 +398,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- public void testAboveShelfChangedListenerCalledHeadsUpGoingAway() throws Exception {
+ public void testAboveShelfChangedListenerCalledHeadsUpAnimatingAway() throws Exception {
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
row.setAboveShelfChangedListener(listener);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
index c06554573bd7..9ce9ff2faf21 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
val Kosmos.bouncerInteractor by Fixture {
BouncerInteractor(
@@ -33,5 +34,6 @@ val Kosmos.bouncerInteractor by Fixture {
deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
falsingInteractor = falsingInteractor,
powerInteractor = powerInteractor,
+ sceneInteractor = sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index 0f6c7cf13211..c3dad748064d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -18,6 +18,7 @@
package com.android.systemui.bouncer.ui.viewmodel
+import android.app.admin.devicePolicyManager
import android.content.applicationContext
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
@@ -31,7 +32,6 @@ import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
-import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.bouncerViewModel by Fixture {
@@ -44,12 +44,12 @@ val Kosmos.bouncerViewModel by Fixture {
simBouncerInteractor = simBouncerInteractor,
authenticationInteractor = authenticationInteractor,
selectedUserInteractor = selectedUserInteractor,
+ devicePolicyManager = devicePolicyManager,
+ bouncerMessageViewModel = bouncerMessageViewModel,
flags = composeBouncerFlags,
selectedUser = userSwitcherViewModel.selectedUser,
users = userSwitcherViewModel.users,
userSwitcherMenu = userSwitcherViewModel.menu,
actionButton = bouncerActionButtonInteractor.actionButton,
- devicePolicyManager = mock(),
- bouncerMessageViewModel = bouncerMessageViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 185deda950c6..6cc1e8eba73d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -20,13 +20,11 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testDispatcher
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
Kosmos.Fixture {
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
- mainDispatcher = testDispatcher,
repository = keyguardTransitionRepository,
keyguardRepository = keyguardRepository,
fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
index f7de5a4c20c7..1a05d21cc30a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowKosmos.kt
@@ -22,14 +22,10 @@ import com.android.keyguard.logging.keyguardTransitionAnimationLogger
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.keyguardTransitionAnimationFlow by Fixture {
KeyguardTransitionAnimationFlow(
- scope = applicationCoroutineScope,
- mainDispatcher = testDispatcher,
transitionInteractor = keyguardTransitionInteractor,
logger = keyguardTransitionAnimationLogger,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
index 165c9429c917..dc1b9feea88f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/data/repository/HeadsUpNotificationRepositoryKosmos.kt
@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
val Kosmos.headsUpNotificationRepository by Fixture { FakeHeadsUpNotificationRepository() }
class FakeHeadsUpNotificationRepository : HeadsUpRepository {
- override val headsUpAnimatingAway: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isHeadsUpAnimatingAway: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val topHeadsUpRow: Flow<HeadsUpRowRepository?> = MutableStateFlow(null)
override val activeHeadsUpRows: MutableStateFlow<Set<HeadsUpRowRepository>> =
MutableStateFlow(emptySet())
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt
new file mode 100644
index 000000000000..64fed689d7a9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/truth/TruthUtils.kt
@@ -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.systemui.truth
+
+import com.google.common.truth.MapSubject
+import com.google.common.truth.Ordered
+
+fun MapSubject.containsEntriesExactly(entry: Pair<*, *>, vararg entries: Pair<*, *>): Ordered =
+ containsExactly(
+ entry.first,
+ entry.second,
+ *entries
+ .asSequence()
+ .flatMap { (key, value) -> sequenceOf(key, value) }
+ .toList()
+ .toTypedArray()
+ )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
index d3410737a432..348a02e1da04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt
@@ -24,6 +24,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl
import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey
@@ -44,6 +45,8 @@ val Kosmos.componentsFactory: ComponentsFactory by
var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture()
var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by
Kosmos.Fixture { componentByKey.keys }
+var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by
+ Kosmos.Fixture { emptySet<VolumePanelStartable>() }
val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by
Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } }
val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
index 49041ed0d652..e5f5d4e389f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt
@@ -22,10 +22,12 @@ import com.android.systemui.volume.panel.componentsFactory
import com.android.systemui.volume.panel.componentsInteractor
import com.android.systemui.volume.panel.componentsLayoutManager
import com.android.systemui.volume.panel.dagger.VolumePanelComponent
+import com.android.systemui.volume.panel.domain.VolumePanelStartable
import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor
import com.android.systemui.volume.panel.ui.composable.ComponentsFactory
import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager
import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
+import com.android.systemui.volume.panel.volumePanelStartables
import kotlinx.coroutines.CoroutineScope
class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory {
@@ -41,5 +43,8 @@ class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePane
override fun componentsLayoutManager(): ComponentsLayoutManager =
kosmos.componentsLayoutManager
+
+ override fun volumePanelStartables(): Set<VolumePanelStartable> =
+ kosmos.volumePanelStartables
}
}
diff --git a/services/Android.bp b/services/Android.bp
index 623519521a5a..cd974c5f562d 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -324,34 +324,34 @@ non_updatable_exportable_droidstubs {
baseline_file: "api/lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/system-server/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system-server/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 20816a1b22c8..73300e45ed43 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -44,6 +44,9 @@ import com.android.server.am.EventLogTags;
import com.android.server.pm.ApexManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.TypePattern;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
import dalvik.system.PathClassLoader;
@@ -207,6 +210,11 @@ public final class SystemServiceManager implements Dumpable {
* @throws RuntimeException if the service fails to start.
*/
@android.ravenwood.annotation.RavenwoodKeep
+ @UsesReflection(
+ @KeepTarget(
+ instanceOfClassConstantExclusive = SystemService.class,
+ methodName = "<init>",
+ methodParameterTypePatterns = {@TypePattern(constant = Context.class)}))
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
final String name = serviceClass.getName();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ad15ea90c45c..8022eb37fce7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -497,6 +497,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -629,6 +631,9 @@ public class ActivityManagerService extends IActivityManager.Stub
private static final int MAX_BUGREPORT_TITLE_SIZE = 100;
private static final int MAX_BUGREPORT_DESCRIPTION_SIZE = 150;
+ private static final DateTimeFormatter DROPBOX_TIME_FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSZ");
+
OomAdjuster mOomAdjuster;
static final String EXTRA_TITLE = "android.intent.extra.TITLE";
@@ -2167,22 +2172,25 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
static class VolatileDropboxEntryStates {
private final Boolean mIsProcessFrozen;
+ private final ZonedDateTime mTimestamp;
- private VolatileDropboxEntryStates(Boolean frozenState) {
+ private VolatileDropboxEntryStates(Boolean frozenState, ZonedDateTime timestamp) {
this.mIsProcessFrozen = frozenState;
+ this.mTimestamp = timestamp;
}
- public static VolatileDropboxEntryStates withProcessFrozenState(boolean frozenState) {
- return new VolatileDropboxEntryStates(frozenState);
- }
-
- public static VolatileDropboxEntryStates emptyVolatileDropboxEnytyStates() {
- return new VolatileDropboxEntryStates(null);
+ public static VolatileDropboxEntryStates withProcessFrozenStateAndTimestamp(
+ boolean frozenState, ZonedDateTime timestamp) {
+ return new VolatileDropboxEntryStates(frozenState, timestamp);
}
public Boolean isProcessFrozen() {
return mIsProcessFrozen;
}
+
+ public ZonedDateTime getTimestamp() {
+ return mTimestamp;
+ }
}
static class MemBinder extends Binder {
@@ -9678,6 +9686,11 @@ public class ActivityManagerService extends IActivityManager.Stub
? volatileStates.isProcessFrozen() : process.mOptRecord.isFrozen()
).append("\n");
}
+ if (volatileStates != null && volatileStates.getTimestamp() != null) {
+ String formattedTime = DROPBOX_TIME_FORMATTER.format(
+ volatileStates.getTimestamp());
+ sb.append("Timestamp: ").append(formattedTime).append("\n");
+ }
int flags = process.info.flags;
final IPackageManager pm = AppGlobals.getPackageManager();
sb.append("Flags: 0x").append(Integer.toHexString(flags)).append("\n");
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 0aa1a69334d7..76c59520d4ea 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -66,7 +66,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@@ -79,9 +79,6 @@ import java.util.concurrent.atomic.AtomicLong;
* The error state of the process, such as if it's crashing/ANR etc.
*/
class ProcessErrorStateRecord {
- private static final DateTimeFormatter DROPBOX_TIME_FORMATTER =
- DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSZ");
-
final ProcessRecord mApp;
private final ActivityManagerService mService;
@@ -355,9 +352,18 @@ class ProcessErrorStateRecord {
synchronized (mProcLock) {
latencyTracker.waitingOnProcLockEnded();
setNotResponding(true);
+
+ ZonedDateTime timestamp = null;
+ if (timeoutRecord != null && timeoutRecord.mEndUptimeMillis > 0) {
+ long millisSinceEndUptimeMs = anrTime - timeoutRecord.mEndUptimeMillis;
+ timestamp = Instant.now().minusMillis(millisSinceEndUptimeMs)
+ .atZone(ZoneId.systemDefault());
+ }
+
volatileDropboxEntriyStates =
ActivityManagerService.VolatileDropboxEntryStates
- .withProcessFrozenState(mApp.mOptRecord.isFrozen());
+ .withProcessFrozenStateAndTimestamp(
+ mApp.mOptRecord.isFrozen(), timestamp);
}
// Log the ANR to the event log.
@@ -450,13 +456,6 @@ class ProcessErrorStateRecord {
info.append("ErrorId: ").append(errorId.toString()).append("\n");
}
info.append("Frozen: ").append(mApp.mOptRecord.isFrozen()).append("\n");
- if (timeoutRecord != null && timeoutRecord.mEndUptimeMillis > 0) {
- long millisSinceEndUptimeMs = anrTime - timeoutRecord.mEndUptimeMillis;
- String formattedTime = DROPBOX_TIME_FORMATTER.format(
- Instant.now().minusMillis(millisSinceEndUptimeMs)
- .atZone(ZoneId.systemDefault()));
- info.append("Timestamp: ").append(formattedTime).append("\n");
- }
// Retrieve controller with max ANR delay from AnrControllers
// Note that we retrieve the controller before dumping stacks because dumping stacks can
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 3e3ec17eb0e0..1f89ca70ce8d 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -170,6 +170,7 @@ public class SettingsToPropertiesMapper {
"pixel_biometrics_face",
"pixel_bluetooth",
"pixel_connectivity_gps",
+ "pixel_continuity",
"pixel_sensors",
"pixel_system_sw_video",
"pixel_watch",
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index debd9d0f0c83..be39778372ca 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1364,6 +1364,9 @@ public class AppOpsService extends IAppOpsService.Stub {
@GuardedBy("this")
private void packageRemovedLocked(int uid, String packageName) {
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+ mHistoricalRegistry, uid, packageName));
+
UidState uidState = mUidStates.get(uid);
if (uidState == null) {
return;
@@ -1398,9 +1401,6 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
-
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
- mHistoricalRegistry, uid, packageName));
}
public void uidRemoved(int uid) {
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
new file mode 100644
index 000000000000..7890fe0ed461
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -0,0 +1,464 @@
+/*
+ * 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.inputmethod;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.annotation.BinderThread;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IBooleanListener;
+import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
+import com.android.internal.inputmethod.IImeTracker;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IInputMethodManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.List;
+
+/**
+ * An actual implementation class of {@link IInputMethodManager.Stub} to allow other classes to
+ * focus on handling IPC callbacks.
+ */
+final class IInputMethodManagerImpl extends IInputMethodManager.Stub {
+
+ /**
+ * Tells that the given permission is already verified before the annotated method gets called.
+ */
+ @Retention(SOURCE)
+ @Target({METHOD})
+ @interface PermissionVerified {
+ String value() default "";
+ }
+
+ @BinderThread
+ interface Callback {
+ void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
+ int selfReportedDisplayId);
+
+ InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId);
+
+ List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
+ @DirectBootAwareness int directBootAwareness);
+
+ List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId);
+
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId);
+
+ InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId);
+
+ boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason);
+
+ boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ void hideSoftInputFromServerForTest();
+
+ void startInputOrWindowGainedFocusAsync(
+ @StartInputReason int startInputReason, IInputMethodClient client,
+ IBinder windowToken, @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+ @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq);
+
+ InputBindResult startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason, IInputMethodClient client,
+ IBinder windowToken, @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+ @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher);
+
+ void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode);
+
+ @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ boolean isInputMethodPickerShownForTest();
+
+ InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId);
+
+ void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId);
+
+ void setExplicitlyEnabledInputMethodSubtypes(String imeId,
+ @NonNull int[] subtypeHashCodes, @UserIdInt int userId);
+
+ int getInputMethodWindowVisibleHeight(IInputMethodClient client);
+
+ void reportPerceptibleAsync(IBinder windowToken, boolean perceptible);
+
+ @PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ void removeImeSurface();
+
+ void removeImeSurfaceFromWindowAsync(IBinder windowToken);
+
+ void startProtoDump(byte[] bytes, int i, String s);
+
+ boolean isImeTraceEnabled();
+
+ @PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
+ void startImeTrace();
+
+ @PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
+ void stopImeTrace();
+
+ void startStylusHandwriting(IInputMethodClient client);
+
+ void startConnectionlessStylusHandwriting(IInputMethodClient client, @UserIdInt int userId,
+ @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
+ @Nullable String delegatorPackageName,
+ @NonNull IConnectionlessHandwritingCallback callback);
+
+ boolean acceptStylusHandwritingDelegation(@NonNull IInputMethodClient client,
+ @UserIdInt int userId, @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags);
+
+ void acceptStylusHandwritingDelegationAsync(@NonNull IInputMethodClient client,
+ @UserIdInt int userId, @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback);
+
+ void prepareStylusHandwritingDelegation(@NonNull IInputMethodClient client,
+ @UserIdInt int userId, @NonNull String delegatePackageName,
+ @NonNull String delegatorPackageName);
+
+ boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId, boolean connectionless);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ void addVirtualStylusIdForTestSession(IInputMethodClient client);
+
+ @PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout);
+
+ IImeTracker getImeTrackerService();
+
+ void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args,
+ @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver,
+ @NonNull Binder self);
+
+ void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout, @Nullable String[] args);
+ }
+
+ @NonNull
+ private final Callback mCallback;
+
+ private IInputMethodManagerImpl(@NonNull Callback callback) {
+ mCallback = callback;
+ }
+
+ static IInputMethodManagerImpl create(@NonNull Callback callback) {
+ return new IInputMethodManagerImpl(callback);
+ }
+
+ @Override
+ public void addClient(IInputMethodClient client, IRemoteInputConnection inputmethod,
+ int untrustedDisplayId) {
+ mCallback.addClient(client, inputmethod, untrustedDisplayId);
+ }
+
+ @Override
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
+ return mCallback.getCurrentInputMethodInfoAsUser(userId);
+ }
+
+ @Override
+ public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
+ int directBootAwareness) {
+ return mCallback.getInputMethodList(userId, directBootAwareness);
+ }
+
+ @Override
+ public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
+ return mCallback.getEnabledInputMethodList(userId);
+ }
+
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+ return mCallback.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
+ userId);
+ }
+
+ @Override
+ public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
+ return mCallback.getLastInputMethodSubtype(userId);
+ }
+
+ @Override
+ public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
+ @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
+ return mCallback.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType,
+ resultReceiver, reason);
+ }
+
+ @Override
+ public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ return mCallback.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver,
+ reason);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public void hideSoftInputFromServerForTest() {
+ super.hideSoftInputFromServerForTest_enforcePermission();
+
+ mCallback.hideSoftInputFromServerForTest();
+ }
+
+ @Override
+ public InputBindResult startInputOrWindowGainedFocus(
+ @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ return mCallback.startInputOrWindowGainedFocus(
+ startInputReason, client, windowToken, startInputFlags, softInputMode,
+ windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
+ unverifiedTargetSdkVersion, userId, imeDispatcher);
+ }
+
+ @Override
+ public void startInputOrWindowGainedFocusAsync(@StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, @Nullable EditorInfo editorInfo,
+ IRemoteInputConnection inputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, @UserIdInt int userId,
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
+ mCallback.startInputOrWindowGainedFocusAsync(
+ startInputReason, client, windowToken, startInputFlags, softInputMode,
+ windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection,
+ unverifiedTargetSdkVersion, userId, imeDispatcher, startInputSeq);
+ }
+
+ @Override
+ public void showInputMethodPickerFromClient(IInputMethodClient client,
+ int auxiliarySubtypeMode) {
+ mCallback.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
+ }
+
+ @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @Override
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
+ super.showInputMethodPickerFromSystem_enforcePermission();
+
+ mCallback.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
+
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public boolean isInputMethodPickerShownForTest() {
+ super.isInputMethodPickerShownForTest_enforcePermission();
+
+ return mCallback.isInputMethodPickerShownForTest();
+ }
+
+ @Override
+ public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
+ return mCallback.getCurrentInputMethodSubtype(userId);
+ }
+
+ @Override
+ public void setAdditionalInputMethodSubtypes(String id, InputMethodSubtype[] subtypes,
+ @UserIdInt int userId) {
+ mCallback.setAdditionalInputMethodSubtypes(id, subtypes, userId);
+ }
+
+ @Override
+ public void setExplicitlyEnabledInputMethodSubtypes(String imeId, int[] subtypeHashCodes,
+ @UserIdInt int userId) {
+ mCallback.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+ }
+
+ @Override
+ public int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
+ return mCallback.getInputMethodWindowVisibleHeight(client);
+ }
+
+ @Override
+ public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+ mCallback.reportPerceptibleAsync(windowToken, perceptible);
+ }
+
+ @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @Override
+ public void removeImeSurface() {
+ super.removeImeSurface_enforcePermission();
+
+ mCallback.removeImeSurface();
+ }
+
+ @Override
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
+ mCallback.removeImeSurfaceFromWindowAsync(windowToken);
+ }
+
+ @Override
+ public void startProtoDump(byte[] protoDump, int source, String where) {
+ mCallback.startProtoDump(protoDump, source, where);
+ }
+
+ @Override
+ public boolean isImeTraceEnabled() {
+ return mCallback.isImeTraceEnabled();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void startImeTrace() {
+ super.startImeTrace_enforcePermission();
+
+ mCallback.startImeTrace();
+ }
+
+ @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @Override
+ public void stopImeTrace() {
+ super.stopImeTrace_enforcePermission();
+
+ mCallback.stopImeTrace();
+ }
+
+ @Override
+ public void startStylusHandwriting(IInputMethodClient client) {
+ mCallback.startStylusHandwriting(client);
+ }
+
+ @Override
+ public void startConnectionlessStylusHandwriting(IInputMethodClient client,
+ @UserIdInt int userId, CursorAnchorInfo cursorAnchorInfo,
+ String delegatePackageName, String delegatorPackageName,
+ IConnectionlessHandwritingCallback callback) {
+ mCallback.startConnectionlessStylusHandwriting(client, userId, cursorAnchorInfo,
+ delegatePackageName, delegatorPackageName, callback);
+ }
+
+ @Override
+ public void prepareStylusHandwritingDelegation(IInputMethodClient client, @UserIdInt int userId,
+ String delegatePackageName, String delegatorPackageName) {
+ mCallback.prepareStylusHandwritingDelegation(client, userId,
+ delegatePackageName, delegatorPackageName);
+ }
+
+ @Override
+ public boolean acceptStylusHandwritingDelegation(IInputMethodClient client,
+ @UserIdInt int userId, String delegatePackageName, String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags) {
+ return mCallback.acceptStylusHandwritingDelegation(client, userId,
+ delegatePackageName, delegatorPackageName, flags);
+ }
+
+ @Override
+ public void acceptStylusHandwritingDelegationAsync(IInputMethodClient client,
+ @UserIdInt int userId, String delegatePackageName, String delegatorPackageName,
+ @InputMethodManager.HandwritingDelegateFlags int flags,
+ IBooleanListener callback) {
+ mCallback.acceptStylusHandwritingDelegationAsync(client, userId,
+ delegatePackageName, delegatorPackageName, flags, callback);
+ }
+
+ @Override
+ public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId,
+ boolean connectionless) {
+ return mCallback.isStylusHandwritingAvailableAsUser(userId, connectionless);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
+ super.addVirtualStylusIdForTestSession_enforcePermission();
+
+ mCallback.addVirtualStylusIdForTestSession(client);
+ }
+
+ @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @Override
+ public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) {
+ super.setStylusWindowIdleTimeoutForTest_enforcePermission();
+
+ mCallback.setStylusWindowIdleTimeoutForTest(client, timeout);
+ }
+
+ @Override
+ public IImeTracker getImeTrackerService() {
+ return mCallback.getImeTrackerService();
+ }
+
+ @Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) {
+ mCallback.onShellCommand(in, out, err, args, callback, resultReceiver, this);
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mCallback.dump(fd, pw, args);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1d07eee927b2..03a85c40ef31 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -61,7 +61,6 @@ import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
-import android.annotation.EnforcePermission;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -172,7 +171,6 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
-import com.android.internal.view.IInputMethodManager;
import com.android.server.AccessibilityManagerInternal;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -211,8 +209,9 @@ import java.util.function.IntConsumer;
/**
* This class provides a system service that manages input methods.
*/
-public final class InputMethodManagerService extends IInputMethodManager.Stub
- implements Handler.Callback {
+public final class InputMethodManagerService implements IInputMethodManagerImpl.Callback,
+ ZeroJankProxy.Callback, Handler.Callback {
+
// Virtual device id for test.
private static final Integer VIRTUAL_STYLUS_ID_FOR_TEST = 999999;
static final boolean DEBUG = false;
@@ -1236,21 +1235,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void onStart() {
mService.publishLocalService();
- IInputMethodManager.Stub service;
+ IInputMethodManagerImpl.Callback service;
if (Flags.useZeroJankProxy()) {
- service =
- new ZeroJankProxy(
- mService.mHandler::post,
- mService,
- () -> {
- synchronized (ImfLock.class) {
- return mService.isInputShown();
- }
- });
+ service = new ZeroJankProxy(mService.mHandler::post, mService);
} else {
service = mService;
}
- publishBinderService(Context.INPUT_METHOD_SERVICE, service, false /*allowIsolated*/,
+ publishBinderService(Context.INPUT_METHOD_SERVICE,
+ IInputMethodManagerImpl.create(service), false /*allowIsolated*/,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
}
@@ -1935,10 +1927,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@Nullable
- ClientState getClientState(IInputMethodClient client) {
- synchronized (ImfLock.class) {
- return mClientController.getClient(client.asBinder());
- }
+ @GuardedBy("ImfLock.class")
+ @Override
+ public ClientState getClientStateLocked(IInputMethodClient client) {
+ return mClientController.getClient(client.asBinder());
}
// TODO(b/314150112): Move this to ClientController.
@@ -2017,7 +2009,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@GuardedBy("ImfLock.class")
- private boolean isInputShown() {
+ @Override
+ public boolean isInputShownLocked() {
return mVisibilityStateComputer.isInputShown();
}
@@ -3133,11 +3126,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
@Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
@Nullable String delegatorPackageName,
- @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+ @NonNull IConnectionlessHandwritingCallback callback) {
synchronized (ImfLock.class) {
if (!mBindingController.supportsConnectionlessStylusHandwriting()) {
Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME.");
- callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
+ try {
+ callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED", e);
+ e.rethrowAsRuntimeException();
+ }
return;
}
}
@@ -3148,7 +3146,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (ImfLock.class) {
if (!mClientController.verifyClientAndPackageMatch(client, delegatorPackageName)) {
Slog.w(TAG, "startConnectionlessStylusHandwriting() fail");
- callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ try {
+ callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
+ e.rethrowAsRuntimeException();
+ }
throw new IllegalArgumentException("Delegator doesn't match UID");
}
}
@@ -3172,7 +3175,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
if (!startStylusHandwriting(
client, false, immsCallback, cursorAnchorInfo, isForDelegation)) {
- callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ try {
+ callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e);
+ e.rethrowAsRuntimeException();
+ }
}
}
@@ -3272,11 +3280,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName,
- @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
- throws RemoteException {
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) {
boolean result = acceptStylusHandwritingDelegation(
client, userId, delegatePackageName, delegatorPackageName, flags);
- callback.onResult(result);
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to report result=" + result, e);
+ e.rethrowAsRuntimeException();
+ }
}
@Override
@@ -3367,7 +3379,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@GuardedBy("ImfLock.class")
boolean showCurrentInputLocked(IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
- int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+ @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason) {
if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
return false;
@@ -3413,7 +3425,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#hideSoftInput");
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) {
- if (isInputShown()) {
+ if (isInputShownLocked()) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED);
} else {
@@ -3436,10 +3448,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@Override
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public void hideSoftInputFromServerForTest() {
- super.hideSoftInputFromServerForTest_enforcePermission();
-
synchronized (ImfLock.class) {
hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
@@ -3472,7 +3482,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
IInputMethodInvoker curMethod = getCurMethodLocked();
final boolean shouldHideSoftInput = curMethod != null
- && (isInputShown() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
+ && (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
mVisibilityStateComputer.requestImeVisibility(windowToken, false);
if (shouldHideSoftInput) {
@@ -3845,13 +3855,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
- super.showInputMethodPickerFromSystem_enforcePermission();
-
mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
.sendToTarget();
}
@@ -3859,10 +3867,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
/**
* A test API for CTS to make sure that the input method menu is showing.
*/
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public boolean isInputMethodPickerShownForTest() {
- super.isInputMethodPickerShownForTest_enforcePermission();
-
synchronized (ImfLock.class) {
return mMenuController.isisInputMethodPickerShownForTestLocked();
}
@@ -4162,11 +4168,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
});
}
- @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
public void removeImeSurface() {
- super.removeImeSurface_enforcePermission();
-
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
@@ -4275,11 +4279,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
* a stylus deviceId is not already registered on device.
*/
@BinderThread
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@Override
public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
- super.addVirtualStylusIdForTestSession_enforcePermission();
-
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession",
@@ -4302,12 +4304,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
* @param timeout to set in milliseconds. To reset to default, use a value <= zero.
*/
@BinderThread
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@Override
public void setStylusWindowIdleTimeoutForTest(
IInputMethodClient client, @DurationMillisLong long timeout) {
- super.setStylusWindowIdleTimeoutForTest_enforcePermission();
-
int uid = Binder.getCallingUid();
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest",
@@ -4403,10 +4403,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@BinderThread
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
public void startImeTrace() {
- super.startImeTrace_enforcePermission();
ImeTracing.getInstance().startTrace(null /* printwriter */);
synchronized (ImfLock.class) {
mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(true /* enabled */));
@@ -4414,11 +4413,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
@BinderThread
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
public void stopImeTrace() {
- super.stopImeTrace_enforcePermission();
-
ImeTracing.getInstance().stopTrace(null /* printwriter */);
synchronized (ImfLock.class) {
mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(false /* enabled */));
@@ -4698,7 +4695,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// implemented so that auxiliary subtypes will be excluded when the soft
// keyboard is invisible.
synchronized (ImfLock.class) {
- showAuxSubtypes = isInputShown();
+ showAuxSubtypes = isInputShownLocked();
}
break;
case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
@@ -5845,7 +5842,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@BinderThread
@Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
PriorityDump.dump(mPriorityDumper, fd, pw, args);
@@ -5975,7 +5972,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
@Nullable FileDescriptor err,
@NonNull String[] args, @Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ @NonNull ResultReceiver resultReceiver, @NonNull Binder self) {
final int callingUid = Binder.getCallingUid();
// Reject any incoming calls from non-shell users, including ones from the system user.
if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) {
@@ -5996,7 +5993,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
throw new SecurityException(errorMsg);
}
new ShellCommandImpl(this).exec(
- this, in, out, err, args, callback, resultReceiver);
+ self, in, out, err, args, callback, resultReceiver);
}
private static final class ShellCommandImpl extends ShellCommand {
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index ffc2319b60af..1cd1ddce78fd 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -36,17 +36,16 @@ import static com.android.server.inputmethod.InputMethodManagerService.TAG;
import android.Manifest;
import android.annotation.BinderThread;
-import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.os.Binder;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.util.Slog;
+import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
@@ -56,6 +55,7 @@ import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.window.ImeOnBackInvokedDispatcher;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
@@ -76,22 +76,25 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.function.BooleanSupplier;
/**
* A proxy that processes all {@link IInputMethodManager} calls asynchronously.
- * @hide
*/
-public class ZeroJankProxy extends IInputMethodManager.Stub {
+final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
- private final IInputMethodManager mInner;
+ interface Callback extends IInputMethodManagerImpl.Callback {
+ @GuardedBy("ImfLock.class")
+ ClientState getClientStateLocked(IInputMethodClient client);
+ @GuardedBy("ImfLock.class")
+ boolean isInputShownLocked();
+ }
+
+ private final Callback mInner;
private final Executor mExecutor;
- private final BooleanSupplier mIsInputShown;
- ZeroJankProxy(Executor executor, IInputMethodManager inner, BooleanSupplier isInputShown) {
+ ZeroJankProxy(Executor executor, Callback inner) {
mInner = inner;
mExecutor = executor;
- mIsInputShown = isInputShown;
}
private void offload(ThrowingRunnable r) {
@@ -126,45 +129,43 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@Override
public void addClient(IInputMethodClient client, IRemoteInputConnection inputConnection,
- int selfReportedDisplayId) throws RemoteException {
+ int selfReportedDisplayId) {
offload(() -> mInner.addClient(client, inputConnection, selfReportedDisplayId));
}
@Override
- public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) throws RemoteException {
+ public InputMethodInfo getCurrentInputMethodInfoAsUser(int userId) {
return mInner.getCurrentInputMethodInfoAsUser(userId);
}
@Override
public List<InputMethodInfo> getInputMethodList(
- int userId, @DirectBootAwareness int directBootAwareness) throws RemoteException {
+ int userId, @DirectBootAwareness int directBootAwareness) {
return mInner.getInputMethodList(userId, directBootAwareness);
}
@Override
- public List<InputMethodInfo> getEnabledInputMethodList(int userId) throws RemoteException {
+ public List<InputMethodInfo> getEnabledInputMethodList(int userId) {
return mInner.getEnabledInputMethodList(userId);
}
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
- boolean allowsImplicitlyEnabledSubtypes, int userId)
- throws RemoteException {
+ boolean allowsImplicitlyEnabledSubtypes, int userId) {
return mInner.getEnabledInputMethodSubtypeList(imiId, allowsImplicitlyEnabledSubtypes,
userId);
}
@Override
- public InputMethodSubtype getLastInputMethodSubtype(int userId) throws RemoteException {
+ public InputMethodSubtype getLastInputMethodSubtype(int userId) {
return mInner.getLastInputMethodSubtype(userId);
}
@Override
public boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
@Nullable ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
- int lastClickTooType, ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason)
- throws RemoteException {
+ @MotionEvent.ToolType int lastClickToolType, ResultReceiver resultReceiver,
+ @SoftInputShowHideReason int reason) {
offload(
() -> {
if (!mInner.showSoftInput(
@@ -172,7 +173,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
windowToken,
statsToken,
flags,
- lastClickTooType,
+ lastClickToolType,
resultReceiver,
reason)) {
sendResultReceiverFailure(resultReceiver);
@@ -184,8 +185,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@Override
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@Nullable ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
- ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)
- throws RemoteException {
+ ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
offload(
() -> {
if (!mInner.hideSoftInput(
@@ -200,17 +200,19 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
if (resultReceiver == null) {
return;
}
- resultReceiver.send(
- mIsInputShown.getAsBoolean()
+ final boolean isInputShown;
+ synchronized (ImfLock.class) {
+ isInputShown = mInner.isInputShownLocked();
+ }
+ resultReceiver.send(isInputShown
? InputMethodManager.RESULT_UNCHANGED_SHOWN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN,
null);
}
@Override
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
- public void hideSoftInputFromServerForTest() throws RemoteException {
- super.hideSoftInputFromServerForTest_enforcePermission();
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
+ public void hideSoftInputFromServerForTest() {
mInner.hideSoftInputFromServerForTest();
}
@@ -225,8 +227,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq)
- throws RemoteException {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher, int startInputSeq) {
offload(() -> {
InputBindResult result = mInner.startInputOrWindowGainedFocus(startInputReason, client,
windowToken, startInputFlags, softInputMode, windowFlags,
@@ -249,99 +250,92 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
IRemoteInputConnection inputConnection,
IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
int unverifiedTargetSdkVersion, @UserIdInt int userId,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher)
- throws RemoteException {
+ @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
// Should never be called when flag is enabled i.e. when this proxy is used.
return null;
}
@Override
public void showInputMethodPickerFromClient(IInputMethodClient client,
- int auxiliarySubtypeMode)
- throws RemoteException {
+ int auxiliarySubtypeMode) {
offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
@Override
- public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)
- throws RemoteException {
+ public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
}
- @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
@Override
- public boolean isInputMethodPickerShownForTest() throws RemoteException {
- super.isInputMethodPickerShownForTest_enforcePermission();
+ public boolean isInputMethodPickerShownForTest() {
return mInner.isInputMethodPickerShownForTest();
}
@Override
- public InputMethodSubtype getCurrentInputMethodSubtype(int userId) throws RemoteException {
+ public InputMethodSubtype getCurrentInputMethodSubtype(int userId) {
return mInner.getCurrentInputMethodSubtype(userId);
}
@Override
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes,
- @UserIdInt int userId) throws RemoteException {
+ @UserIdInt int userId) {
mInner.setAdditionalInputMethodSubtypes(imiId, subtypes, userId);
}
@Override
public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
- @NonNull int[] subtypeHashCodes, @UserIdInt int userId) throws RemoteException {
+ @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
mInner.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
}
@Override
- public int getInputMethodWindowVisibleHeight(IInputMethodClient client)
- throws RemoteException {
+ public int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
return mInner.getInputMethodWindowVisibleHeight(client);
}
@Override
- public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible)
- throws RemoteException {
+ public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
// Already async TODO(b/293640003): ordering issues?
mInner.reportPerceptibleAsync(windowToken, perceptible);
}
- @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
@Override
- public void removeImeSurface() throws RemoteException {
+ public void removeImeSurface() {
mInner.removeImeSurface();
}
@Override
- public void removeImeSurfaceFromWindowAsync(IBinder windowToken) throws RemoteException {
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
mInner.removeImeSurfaceFromWindowAsync(windowToken);
}
@Override
- public void startProtoDump(byte[] bytes, int i, String s) throws RemoteException {
+ public void startProtoDump(byte[] bytes, int i, String s) {
mInner.startProtoDump(bytes, i, s);
}
@Override
- public boolean isImeTraceEnabled() throws RemoteException {
+ public boolean isImeTraceEnabled() {
return mInner.isImeTraceEnabled();
}
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
- public void startImeTrace() throws RemoteException {
+ public void startImeTrace() {
mInner.startImeTrace();
}
- @EnforcePermission(Manifest.permission.CONTROL_UI_TRACING)
+ @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING)
@Override
- public void stopImeTrace() throws RemoteException {
+ public void stopImeTrace() {
mInner.stopImeTrace();
}
@Override
- public void startStylusHandwriting(IInputMethodClient client)
- throws RemoteException {
+ public void startStylusHandwriting(IInputMethodClient client) {
offload(() -> mInner.startStylusHandwriting(client));
}
@@ -349,7 +343,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId,
@Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName,
@Nullable String delegatorPackageName,
- @NonNull IConnectionlessHandwritingCallback callback) throws RemoteException {
+ @NonNull IConnectionlessHandwritingCallback callback) {
offload(() -> mInner.startConnectionlessStylusHandwriting(
client, userId, cursorAnchorInfo, delegatePackageName, delegatorPackageName,
callback));
@@ -363,14 +357,11 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@NonNull String delegatorPackageName,
@InputMethodManager.HandwritingDelegateFlags int flags) {
try {
- return CompletableFuture.supplyAsync(() -> {
- try {
- return mInner.acceptStylusHandwritingDelegation(
- client, userId, delegatePackageName, delegatorPackageName, flags);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- }, this::offload).get();
+ return CompletableFuture.supplyAsync(() ->
+ mInner.acceptStylusHandwritingDelegation(
+ client, userId, delegatePackageName, delegatorPackageName,
+ flags),
+ this::offload).get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
@@ -384,8 +375,7 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
@UserIdInt int userId,
@NonNull String delegatePackageName,
@NonNull String delegatorPackageName,
- @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)
- throws RemoteException {
+ @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) {
offload(() -> mInner.acceptStylusHandwritingDelegationAsync(
client, userId, delegatePackageName, delegatorPackageName, flags, callback));
}
@@ -401,52 +391,45 @@ public class ZeroJankProxy extends IInputMethodManager.Stub {
}
@Override
- public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless)
- throws RemoteException {
+ public boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless) {
return mInner.isStylusHandwritingAvailableAsUser(userId, connectionless);
}
- @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @IInputMethodManagerImpl.PermissionVerified("android.permission.TEST_INPUT_METHOD")
@Override
- public void addVirtualStylusIdForTestSession(IInputMethodClient client)
- throws RemoteException {
+ public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
mInner.addVirtualStylusIdForTestSession(client);
}
- @EnforcePermission("android.permission.TEST_INPUT_METHOD")
+ @IInputMethodManagerImpl.PermissionVerified("android.permission.TEST_INPUT_METHOD")
@Override
- public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout)
- throws RemoteException {
+ public void setStylusWindowIdleTimeoutForTest(IInputMethodClient client, long timeout) {
mInner.setStylusWindowIdleTimeoutForTest(client, timeout);
}
@Override
- public IImeTracker getImeTrackerService() throws RemoteException {
+ public IImeTracker getImeTrackerService() {
return mInner.getImeTrackerService();
}
@BinderThread
@Override
public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
- @Nullable FileDescriptor err,
- @NonNull String[] args, @Nullable ShellCallback callback,
- @NonNull ResultReceiver resultReceiver) throws RemoteException {
- ((InputMethodManagerService) mInner).onShellCommand(
- in, out, err, args, callback, resultReceiver);
+ @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver, @NonNull Binder self) {
+ mInner.onShellCommand(in, out, err, args, callback, resultReceiver, self);
}
@Override
- protected void dump(@NonNull FileDescriptor fd,
- @NonNull PrintWriter fout,
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
- ((InputMethodManagerService) mInner).dump(fd, fout, args);
+ mInner.dump(fd, fout, args);
}
private void sendOnStartInputResult(
IInputMethodClient client, InputBindResult res, int startInputSeq) {
synchronized (ImfLock.class) {
- InputMethodManagerService service = (InputMethodManagerService) mInner;
- final ClientState cs = service.getClientState(client);
+ final ClientState cs = mInner.getClientStateLocked(client);
if (cs != null && cs.mClient != null) {
cs.mClient.onStartInputResult(res, startInputSeq);
} else {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 19562ef79fbb..dbdb155eb2e3 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -950,13 +950,18 @@ public class LockSettingsService extends ILockSettings.Stub {
&& android.multiuser.Flags.enablePrivateSpaceFeatures()
&& android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
mHandler.post(() -> {
- UserProperties userProperties =
- mUserManager.getUserProperties(UserHandle.of(userId));
- if (userProperties != null
- && userProperties.getAllowStoppingUserWithDelayedLocking()) {
- int strongAuthRequired = LockPatternUtils.StrongAuthTracker
- .getDefaultFlags(mContext);
- requireStrongAuth(strongAuthRequired, userId);
+ try {
+ UserProperties userProperties =
+ mUserManager.getUserProperties(UserHandle.of(userId));
+ if (userProperties != null && userProperties
+ .getAllowStoppingUserWithDelayedLocking()) {
+ int strongAuthRequired = LockPatternUtils.StrongAuthTracker
+ .getDefaultFlags(mContext);
+ requireStrongAuth(strongAuthRequired, userId);
+ }
+ } catch (IllegalArgumentException e) {
+ Slogf.d(TAG, "User %d does not exist or has been removed",
+ userId);
}
});
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 1a129cb080a8..064443ce7d10 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -267,9 +267,11 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
public boolean showMediaOutputSwitcher(String packageName) {
- if (!validatePackageName(Binder.getCallingUid(), packageName)) {
+ int uid = Binder.getCallingUid();
+ if (!validatePackageName(uid, packageName)) {
throw new SecurityException("packageName must match the calling identity");
}
+ UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
final long token = Binder.clearCallingIdentity();
try {
if (mContext.getSystemService(ActivityManager.class).getPackageImportance(packageName)
@@ -280,7 +282,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
synchronized (mLock) {
StatusBarManagerInternal statusBar =
LocalServices.getService(StatusBarManagerInternal.class);
- statusBar.showMediaOutputSwitcher(packageName);
+ statusBar.showMediaOutputSwitcher(packageName, userHandle);
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 0cd7654f70ea..dfb2b0a750e3 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -157,6 +157,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
}
@Override
+ public void expireTempEngaged() {
+ // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
+ }
+
+ @Override
public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
KeyEvent ke, int sequenceId, ResultReceiver cb) {
// TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 93f68a831705..194ab04817ec 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -24,6 +24,7 @@ import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_L
import static android.media.session.MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE;
import android.Manifest;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -85,6 +86,8 @@ import com.android.server.LocalServices;
import com.android.server.uri.UriGrantsManagerInternal;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -225,6 +228,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private int mPolicies;
+ private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;
+
+ @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface UserEngagementState {}
+
+ /**
+ * Indicates that the session is active and in one of the user engaged states.
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_PERMANENTLY_ENGAGED = 0;
+
+ /**
+ * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_TEMPORARY_ENGAGED = 1;
+
+ /**
+ * Indicates that the session is either not active or in one of the user disengaged states
+ *
+ * @see #updateUserEngagedStateIfNeededLocked(boolean) ()
+ */
+ private static final int USER_DISENGAGED = 2;
+
+ /**
+ * Indicates the duration of the temporary engaged states.
+ *
+ * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
+ * engaged, meaning the corresponding session is only considered in an engaged state for the
+ * duration of this timeout, and only if coming from an engaged state.
+ *
+ * <p>For example, if a session is transitioning from a user-engaged state {@link
+ * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
+ * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
+ * the duration of this timeout, starting at the transition instant. However, a temporary
+ * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
+ * state {@link PlaybackState#STATE_STOPPED}.
+ */
+ private static final int TEMP_USER_ENGAGED_TIMEOUT = 600000;
+
public MediaSessionRecord(
int ownerPid,
int ownerUid,
@@ -548,6 +594,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
mDestroyed = true;
mPlaybackState = null;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
mHandler.post(MessageHandler.MSG_DESTROYED);
}
}
@@ -559,6 +606,12 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
}
+ @Override
+ public void expireTempEngaged() {
+ mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ }
+
/**
* Sends media button.
*
@@ -1129,6 +1182,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
};
+ private final Runnable mHandleTempEngagedSessionTimeout =
+ () -> {
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ };
+
@RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS)
private static boolean componentNameExists(
@NonNull ComponentName componentName, @NonNull Context context, int userId) {
@@ -1145,6 +1203,40 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
return !resolveInfos.isEmpty();
}
+ private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
+ int oldUserEngagedState = mUserEngagementState;
+ int newUserEngagedState;
+ if (!isActive() || mPlaybackState == null) {
+ newUserEngagedState = USER_DISENGAGED;
+ } else if (isActive() && mPlaybackState.isActive()) {
+ newUserEngagedState = USER_PERMANENTLY_ENGAGED;
+ } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
+ newUserEngagedState =
+ oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
+ ? USER_TEMPORARY_ENGAGED
+ : USER_DISENGAGED;
+ } else {
+ newUserEngagedState = USER_DISENGAGED;
+ }
+ if (oldUserEngagedState == newUserEngagedState) {
+ return;
+ }
+
+ if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
+ mHandler.postDelayed(mHandleTempEngagedSessionTimeout, TEMP_USER_ENGAGED_TIMEOUT);
+ } else if (oldUserEngagedState == USER_TEMPORARY_ENGAGED) {
+ mHandler.removeCallbacks(mHandleTempEngagedSessionTimeout);
+ }
+
+ boolean wasUserEngaged = oldUserEngagedState != USER_DISENGAGED;
+ boolean isNowUserEngaged = newUserEngagedState != USER_DISENGAGED;
+ mUserEngagementState = newUserEngagedState;
+ if (wasUserEngaged != isNowUserEngaged) {
+ mService.onSessionUserEngagementStateChange(
+ /* mediaSessionRecord= */ this, /* isUserEngaged= */ isNowUserEngaged);
+ }
+ }
+
private final class SessionStub extends ISession.Stub {
@Override
public void destroySession() throws RemoteException {
@@ -1182,8 +1274,10 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
.logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
callingUid, callingPid);
}
-
- mIsActive = active;
+ synchronized (mLock) {
+ mIsActive = active;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+ }
long token = Binder.clearCallingIdentity();
try {
mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
@@ -1341,6 +1435,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
&& TRANSITION_PRIORITY_STATES.contains(newState));
synchronized (mLock) {
mPlaybackState = state;
+ updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
}
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 09991995099e..b57b14835987 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -196,6 +196,12 @@ public abstract class MediaSessionRecordImpl {
*/
public abstract boolean isClosed();
+ /**
+ * Note: This method is only used for testing purposes If the session is temporary engaged, the
+ * timeout will expire and it will become disengaged.
+ */
+ public abstract void expireTempEngaged();
+
@Override
public final boolean equals(Object o) {
if (this == o) return true;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 53c32cf31d21..74adf5e0d52c 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -367,11 +367,13 @@ public class MediaSessionService extends SystemService implements Monitor {
}
boolean isUserEngaged = isUserEngaged(record, playbackState);
- Log.d(TAG, "onSessionActiveStateChanged: "
- + "record=" + record
- + "playbackState=" + playbackState
- + "allowRunningInForeground=" + isUserEngaged);
- setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ Log.d(
+ TAG,
+ "onSessionActiveStateChanged:"
+ + " record="
+ + record
+ + " playbackState="
+ + playbackState);
reportMediaInteractionEvent(record, isUserEngaged);
mHandler.postSessionsChanged(record);
}
@@ -479,11 +481,13 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
boolean isUserEngaged = isUserEngaged(record, playbackState);
- Log.d(TAG, "onSessionPlaybackStateChanged: "
- + "record=" + record
- + "playbackState=" + playbackState
- + "allowRunningInForeground=" + isUserEngaged);
- setForegroundServiceAllowance(record, /* allowRunningInForeground= */ isUserEngaged);
+ Log.d(
+ TAG,
+ "onSessionPlaybackStateChanged:"
+ + " record="
+ + record
+ + " playbackState="
+ + playbackState);
reportMediaInteractionEvent(record, isUserEngaged);
}
}
@@ -650,68 +654,112 @@ public class MediaSessionService extends SystemService implements Monitor {
session.close();
Log.d(TAG, "destroySessionLocked: record=" + session);
- setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
+
reportMediaInteractionEvent(session, /* userEngaged= */ false);
mHandler.postSessionsChanged(session);
}
- private void setForegroundServiceAllowance(
- MediaSessionRecordImpl record, boolean allowRunningInForeground) {
- if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
- return;
- }
- ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
- record.getForegroundServiceDelegationOptions();
- if (foregroundServiceDelegationOptions == null) {
- return;
- }
- if (allowRunningInForeground) {
- onUserSessionEngaged(record);
+ void onSessionUserEngagementStateChange(
+ MediaSessionRecordImpl mediaSessionRecord, boolean isUserEngaged) {
+ if (isUserEngaged) {
+ addUserEngagedSession(mediaSessionRecord);
+ startFgsIfSessionIsLinkedToNotification(mediaSessionRecord);
} else {
- onUserDisengaged(record);
+ removeUserEngagedSession(mediaSessionRecord);
+ stopFgsIfNoSessionIsLinkedToNotification(mediaSessionRecord);
}
}
- private void onUserSessionEngaged(MediaSessionRecordImpl mediaSessionRecord) {
+ private void addUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
mUserEngagedSessionsForFgs.putIfAbsent(uid, new HashSet<>());
mUserEngagedSessionsForFgs.get(uid).add(mediaSessionRecord);
+ }
+ }
+
+ private void removeUserEngagedSession(MediaSessionRecordImpl mediaSessionRecord) {
+ synchronized (mLock) {
+ int uid = mediaSessionRecord.getUid();
+ Set<MediaSessionRecordImpl> mUidUserEngagedSessionsForFgs =
+ mUserEngagedSessionsForFgs.get(uid);
+ if (mUidUserEngagedSessionsForFgs == null) {
+ return;
+ }
+
+ mUidUserEngagedSessionsForFgs.remove(mediaSessionRecord);
+ if (mUidUserEngagedSessionsForFgs.isEmpty()) {
+ mUserEngagedSessionsForFgs.remove(uid);
+ }
+ }
+ }
+
+ private void startFgsIfSessionIsLinkedToNotification(
+ MediaSessionRecordImpl mediaSessionRecord) {
+ Log.d(TAG, "startFgsIfSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ synchronized (mLock) {
+ int uid = mediaSessionRecord.getUid();
for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid, Set.of())) {
if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
- mActivityManagerInternal.startForegroundServiceDelegate(
- mediaSessionRecord.getForegroundServiceDelegationOptions(),
- /* connection= */ null);
+ startFgsDelegate(mediaSessionRecord.getForegroundServiceDelegationOptions());
return;
}
}
}
}
- private void onUserDisengaged(MediaSessionRecordImpl mediaSessionRecord) {
+ private void startFgsDelegate(
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mActivityManagerInternal.startForegroundServiceDelegate(
+ foregroundServiceDelegationOptions, /* connection= */ null);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void stopFgsIfNoSessionIsLinkedToNotification(
+ MediaSessionRecordImpl mediaSessionRecord) {
+ Log.d(TAG, "stopFgsIfNoSessionIsLinkedToNotification: record=" + mediaSessionRecord);
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
- if (mUserEngagedSessionsForFgs.containsKey(uid)) {
- mUserEngagedSessionsForFgs.get(uid).remove(mediaSessionRecord);
- if (mUserEngagedSessionsForFgs.get(uid).isEmpty()) {
- mUserEngagedSessionsForFgs.remove(uid);
- }
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
+ mediaSessionRecord.getForegroundServiceDelegationOptions();
+ if (foregroundServiceDelegationOptions == null) {
+ return;
}
- boolean shouldStopFgs = true;
- for (MediaSessionRecordImpl sessionRecord :
+ for (MediaSessionRecordImpl record :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification : mMediaNotifications.getOrDefault(uid,
- Set.of())) {
- if (sessionRecord.isLinkedToNotification(mediaNotification)) {
- shouldStopFgs = false;
+ for (Notification mediaNotification :
+ mMediaNotifications.getOrDefault(uid, Set.of())) {
+ if (record.isLinkedToNotification(mediaNotification)) {
+ // A user engaged session linked with a media notification is found.
+ // We shouldn't call stop FGS in this case.
+ return;
}
}
}
- if (shouldStopFgs) {
- mActivityManagerInternal.stopForegroundServiceDelegate(
- mediaSessionRecord.getForegroundServiceDelegationOptions());
- }
+
+ stopFgsDelegate(foregroundServiceDelegationOptions);
+ }
+ }
+
+ private void stopFgsDelegate(
+ ForegroundServiceDelegationOptions foregroundServiceDelegationOptions) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mActivityManagerInternal.stopForegroundServiceDelegate(
+ foregroundServiceDelegationOptions);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@@ -2502,7 +2550,6 @@ public class MediaSessionService extends SystemService implements Monitor {
}
MediaSessionRecord session = null;
MediaButtonReceiverHolder mediaButtonReceiverHolder = null;
-
if (mCustomMediaKeyDispatcher != null) {
MediaSession.Token token = mCustomMediaKeyDispatcher.getMediaSession(
keyEvent, uid, asSystemService);
@@ -2630,6 +2677,18 @@ public class MediaSessionService extends SystemService implements Monitor {
&& streamType <= AudioManager.STREAM_NOTIFICATION;
}
+ @Override
+ public void expireTempEngagedSessions() {
+ synchronized (mLock) {
+ for (Set<MediaSessionRecordImpl> uidSessions :
+ mUserEngagedSessionsForFgs.values()) {
+ for (MediaSessionRecordImpl sessionRecord : uidSessions) {
+ sessionRecord.expireTempEngaged();
+ }
+ }
+ }
+ }
+
private class MediaKeyListenerResultReceiver extends ResultReceiver implements Runnable {
private final String mPackageName;
private final int mPid;
@@ -3127,7 +3186,6 @@ public class MediaSessionService extends SystemService implements Monitor {
super.onNotificationPosted(sbn);
Notification postedNotification = sbn.getNotification();
int uid = sbn.getUid();
-
if (!postedNotification.isMediaNotification()) {
return;
}
@@ -3138,11 +3196,9 @@ public class MediaSessionService extends SystemService implements Monitor {
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
ForegroundServiceDelegationOptions foregroundServiceDelegationOptions =
mediaSessionRecord.getForegroundServiceDelegationOptions();
- if (mediaSessionRecord.isLinkedToNotification(postedNotification)
- && foregroundServiceDelegationOptions != null) {
- mActivityManagerInternal.startForegroundServiceDelegate(
- foregroundServiceDelegationOptions,
- /* connection= */ null);
+ if (foregroundServiceDelegationOptions != null
+ && mediaSessionRecord.isLinkedToNotification(postedNotification)) {
+ startFgsDelegate(foregroundServiceDelegationOptions);
return;
}
}
@@ -3173,21 +3229,7 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
- boolean shouldStopFgs = true;
- for (MediaSessionRecordImpl mediaSessionRecord :
- mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
- for (Notification mediaNotification :
- mMediaNotifications.getOrDefault(uid, Set.of())) {
- if (mediaSessionRecord.isLinkedToNotification(mediaNotification)) {
- shouldStopFgs = false;
- }
- }
- }
- if (shouldStopFgs
- && notificationRecord.getForegroundServiceDelegationOptions() != null) {
- mActivityManagerInternal.stopForegroundServiceDelegate(
- notificationRecord.getForegroundServiceDelegationOptions());
- }
+ stopFgsIfNoSessionIsLinkedToNotification(notificationRecord);
}
}
diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java
index a56380827f2c..a20de3198d2c 100644
--- a/services/core/java/com/android/server/media/MediaShellCommand.java
+++ b/services/core/java/com/android/server/media/MediaShellCommand.java
@@ -92,6 +92,8 @@ public class MediaShellCommand extends ShellCommand {
runMonitor();
} else if (cmd.equals("volume")) {
runVolume();
+ } else if (cmd.equals("expire-temp-engaged-sessions")) {
+ expireTempEngagedSessions();
} else {
showError("Error: unknown command '" + cmd + "'");
return -1;
@@ -367,4 +369,8 @@ public class MediaShellCommand extends ShellCommand {
private void runVolume() throws Exception {
VolumeCtrl.run(this);
}
+
+ private void expireTempEngagedSessions() throws Exception {
+ mSessionService.expireTempEngagedSessions();
+ }
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ebc1a2a45579..b14242ef8e08 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -22,6 +22,7 @@ import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
import static android.app.Flags.lifetimeExtensionRefactor;
import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
@@ -701,7 +702,7 @@ public class NotificationManagerService extends SystemService {
private static final int MY_UID = Process.myUid();
private static final int MY_PID = Process.myPid();
- private static final IBinder ALLOWLIST_TOKEN = new Binder();
+ static final IBinder ALLOWLIST_TOKEN = new Binder();
protected RankingHandler mRankingHandler;
private long mLastOverRateLogTime;
private float mMaxPackageEnqueueRate = DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
@@ -3598,8 +3599,8 @@ public class NotificationManagerService extends SystemService {
}
}
- @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING)
@Override
+ @EnforcePermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING)
public void setToastRateLimitingEnabled(boolean enable) {
super.setToastRateLimitingEnabled_enforcePermission();
@@ -4524,7 +4525,6 @@ public class NotificationManagerService extends SystemService {
return getActiveNotificationsWithAttribution(callingPkg, null);
}
- @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
/**
* System-only API for getting a list of current (i.e. not cleared) notifications.
*
@@ -4532,6 +4532,7 @@ public class NotificationManagerService extends SystemService {
* @returns A list of all the notifications, in natural order.
*/
@Override
+ @EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public StatusBarNotification[] getActiveNotificationsWithAttribution(String callingPkg,
String callingAttributionTag) {
// enforce() will ensure the calling uid has the correct permission
@@ -4549,9 +4550,9 @@ public class NotificationManagerService extends SystemService {
});
// noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
- callingAttributionTag, null)
- == MODE_ALLOWED) {
+ int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
+ callingAttributionTag, null);
+ if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
synchronized (mNotificationLock) {
final int N = mNotificationList.size();
for (int i = 0; i < N; i++) {
@@ -4627,7 +4628,7 @@ public class NotificationManagerService extends SystemService {
// Remove background token before returning notification to untrusted app, this
// ensures the app isn't able to perform background operations that are
// associated with notification interactions.
- notification.clearAllowlistToken();
+ notification.overrideAllowlistToken(null);
return new StatusBarNotification(
sbn.getPackageName(),
sbn.getOpPkg(),
@@ -4651,12 +4652,12 @@ public class NotificationManagerService extends SystemService {
includeSnoozed);
}
- @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
/**
* System-only API for getting a list of recent (cleared, no longer shown) notifications.
*/
@Override
@RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
+ @EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public StatusBarNotification[] getHistoricalNotificationsWithAttribution(String callingPkg,
String callingAttributionTag, int count, boolean includeSnoozed) {
// enforce() will ensure the calling uid has the correct permission
@@ -4666,9 +4667,9 @@ public class NotificationManagerService extends SystemService {
int uid = Binder.getCallingUid();
// noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
- callingAttributionTag, null)
- == MODE_ALLOWED) {
+ int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
+ callingAttributionTag, null);
+ if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
synchronized (mArchive) {
tmp = mArchive.getArray(mUm, count, includeSnoozed);
}
@@ -4676,7 +4677,6 @@ public class NotificationManagerService extends SystemService {
return tmp;
}
- @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
/**
* System-only API for getting a list of historical notifications. May contain multiple days
* of notifications.
@@ -4684,6 +4684,7 @@ public class NotificationManagerService extends SystemService {
@Override
@WorkerThread
@RequiresPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
+ @EnforcePermission(android.Manifest.permission.ACCESS_NOTIFICATIONS)
public NotificationHistory getNotificationHistory(String callingPkg,
String callingAttributionTag) {
// enforce() will ensure the calling uid has the correct permission
@@ -4691,9 +4692,9 @@ public class NotificationManagerService extends SystemService {
int uid = Binder.getCallingUid();
// noteOp will check to make sure the callingPkg matches the uid
- if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
- callingAttributionTag, null)
- == MODE_ALLOWED) {
+ int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg,
+ callingAttributionTag, null);
+ if (mode == MODE_ALLOWED || mode == MODE_DEFAULT) {
IntArray currentUserIds = mUserProfiles.getCurrentProfileIds();
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryReadHistory");
try {
@@ -7214,6 +7215,17 @@ public class NotificationManagerService extends SystemService {
+ " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);
}
+ if (android.app.Flags.secureAllowlistToken()) {
+ IBinder allowlistToken = notification.getAllowlistToken();
+ if (allowlistToken != null && allowlistToken != ALLOWLIST_TOKEN) {
+ throw new SecurityException(
+ "Unexpected allowlist token received from " + callingUid);
+ }
+ // allowlistToken is populated by unparceling, so it can be null if the notification was
+ // posted from inside system_server. Ensure it's the expected value.
+ notification.overrideAllowlistToken(ALLOWLIST_TOKEN);
+ }
+
checkRestrictedCategories(notification);
// Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE,
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b5d49b36affe..53863aa83ab4 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -622,6 +622,7 @@ public final class PowerManagerService extends SystemService
// Value we store for tracking face down behavior.
@VisibleForTesting
boolean mIsFaceDown = false;
+ private boolean mUseFaceDownDetector = true;
private long mLastFlipTime = 0L;
// The screen brightness setting override from the window manager
@@ -3253,7 +3254,7 @@ public final class PowerManagerService extends SystemService
mScreenTimeoutOverridePolicy.getScreenTimeoutOverrideLocked(
mWakeLockSummary, screenOffTimeout);
}
- if (mIsFaceDown) {
+ if (mIsFaceDown && mUseFaceDownDetector) {
shortestScreenOffTimeout = Math.min(screenDimDuration, shortestScreenOffTimeout);
}
@@ -4701,6 +4702,7 @@ public final class PowerManagerService extends SystemService
pw.println(" mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
pw.println(" mLastFlipTime=" + mLastFlipTime);
pw.println(" mIsFaceDown=" + mIsFaceDown);
+ pw.println(" mUseFaceDownDetector=" + mUseFaceDownDetector);
pw.println();
pw.println("Settings and Configuration:");
@@ -6921,6 +6923,16 @@ public final class PowerManagerService extends SystemService
Binder.restoreCallingIdentity(ident);
}
}
+
+ public void setUseFaceDownDetector(boolean enable) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mUseFaceDownDetector = enable;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/power/PowerManagerShellCommand.java b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
index 9439b762fde0..20184e9fd1a7 100644
--- a/services/core/java/com/android/server/power/PowerManagerShellCommand.java
+++ b/services/core/java/com/android/server/power/PowerManagerShellCommand.java
@@ -63,6 +63,8 @@ class PowerManagerShellCommand extends ShellCommand {
return runListAmbientDisplaySuppressionTokens();
case "set-prox":
return runSetProx();
+ case "set-face-down-detector":
+ return runSetFaceDownDetector();
default:
return handleDefaultCommands(cmd);
}
@@ -178,6 +180,20 @@ class PowerManagerShellCommand extends ShellCommand {
return 0;
}
+ /**
+ * To be used for testing - allowing us to disable the usage of face down detector.
+ */
+ private int runSetFaceDownDetector() {
+ try {
+ mService.setUseFaceDownDetector(Boolean.parseBoolean(getNextArgRequired()));
+ } catch (Exception e) {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Error: " + e);
+ return -1;
+ }
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -203,6 +219,8 @@ class PowerManagerShellCommand extends ShellCommand {
pw.println(" Acquires the proximity sensor wakelock. Wakelock is associated with");
pw.println(" a specific display if specified. 'list' lists wakelocks previously");
pw.println(" created by set-prox including their held status.");
+ pw.println(" set-face-down-detector [true|false]");
+ pw.println(" sets whether we use face down detector timeouts or not");
pw.println();
Intent.printIntentArgsHelp(pw , "");
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 2ff38616fce5..e4f60ec2cdb8 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.UserHandle;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
@@ -248,10 +249,10 @@ public interface StatusBarManagerInternal {
/**
* Shows the media output switcher dialog.
*
- * @param packageName of the session for which the output switcher is shown.
+ * @param targetPackageName of the session for which the output switcher is shown.
* @see com.android.internal.statusbar.IStatusBar#showMediaOutputSwitcher
*/
- void showMediaOutputSwitcher(String packageName);
+ void showMediaOutputSwitcher(String targetPackageName, UserHandle targetUserHandle);
/**
* Add a tile to the Quick Settings Panel
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index cca5beb13405..2c67207f407c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -850,11 +850,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
@Override
- public void showMediaOutputSwitcher(String packageName) {
+ public void showMediaOutputSwitcher(String targetPackageName, UserHandle targetUserHandle) {
IStatusBar bar = mBar;
if (bar != null) {
try {
- bar.showMediaOutputSwitcher(packageName);
+ bar.showMediaOutputSwitcher(targetPackageName, targetUserHandle);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 88f86cc7cecb..a0902cdd7c27 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8967,8 +8967,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
: mDisplayContent.getDisplayInfo();
final Task task = getTask();
task.calculateInsetFrames(mTmpBounds /* outNonDecorBounds */,
- outStableBounds /* outStableBounds */, parentBounds /* bounds */, di,
- true /* useLegacyInsetsForStableBounds */);
+ outStableBounds /* outStableBounds */, parentBounds /* bounds */, di);
final int orientationWithInsets = outStableBounds.height() >= outStableBounds.width()
? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
// If orientation does not match the orientation with insets applied, then a
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e157318543f6..f8aa69b80253 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -912,7 +912,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
IBinder clientToken, @Nullable InputTransferToken hostInputTransferToken, int flags,
- int privateFlags, int type, int inputFeatures, IBinder windowToken,
+ int privateFlags, int inputFeatures, int type, IBinder windowToken,
InputTransferToken inputTransferToken, String inputHandleName,
InputChannel outInputChannel) {
if (hostInputTransferToken == null && !mCanAddInternalSystemWindow) {
@@ -925,7 +925,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
try {
mService.grantInputChannel(this, mUid, mPid, displayId, surface, clientToken,
hostInputTransferToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0,
- type, inputFeatures, windowToken, inputTransferToken, inputHandleName,
+ inputFeatures, type, windowToken, inputTransferToken, inputHandleName,
outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 129af90793b3..218fb7f6b817 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2309,8 +2309,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// area, i.e. the screen area without the system bars.
// The non decor inset are areas that could never be removed in Honeycomb. See
// {@link WindowManagerPolicy#getNonDecorInsetsLw}.
- calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di,
- false /* useLegacyInsetsForStableBounds */);
+ calculateInsetFrames(mTmpNonDecorBounds, mTmpStableBounds, mTmpFullBounds, di);
} else {
// Apply the given non-decor and stable insets to calculate the corresponding bounds
// for screen size of configuration.
@@ -2408,11 +2407,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
* @param outNonDecorBounds where to place bounds with non-decor insets applied.
* @param outStableBounds where to place bounds with stable insets applied.
* @param bounds the bounds to inset.
- * @param useLegacyInsetsForStableBounds {@code true} if we need to use the legacy insets frame
- * for apps targeting U or before when calculating stable bounds.
*/
void calculateInsetFrames(Rect outNonDecorBounds, Rect outStableBounds, Rect bounds,
- DisplayInfo displayInfo, boolean useLegacyInsetsForStableBounds) {
+ DisplayInfo displayInfo) {
outNonDecorBounds.set(bounds);
outStableBounds.set(bounds);
if (mDisplayContent == null) {
@@ -2424,11 +2421,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final DisplayPolicy.DecorInsets.Info info = policy.getDecorInsetsInfo(
displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight);
intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, info.mNonDecorInsets);
- if (!useLegacyInsetsForStableBounds) {
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
- } else {
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
- }
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
}
/**
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 222abc35ee0b..ce53290da49c 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -565,6 +565,9 @@ class TransitionController {
if (isTransientCollect(ar)) {
return true;
}
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isTransientLaunch(ar)) return true;
+ }
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
if (mPlayingTransitions.get(i).isTransientLaunch(ar)) return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7a0245bc1acc..4d9fc6c14bc0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3701,22 +3701,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mDragResizingChangeReported = true;
mWindowFrames.clearReportResizeHints();
- // App window resize may trigger Activity#onConfigurationChanged, so we need to update
- // ActivityWindowInfo as well.
- final IBinder activityToken;
- final ActivityWindowInfo activityWindowInfo;
- if (mLastReportedActivityWindowInfo != null) {
- activityToken = mActivityRecord.token;
- activityWindowInfo = mLastReportedActivityWindowInfo;
- } else {
- activityToken = null;
- activityWindowInfo = null;
- }
-
final int prevRotation = mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation();
fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
- activityWindowInfo, true /* useLatestConfig */, false /* relayoutVisible */);
+ mLastReportedActivityWindowInfo, true /* useLatestConfig */,
+ false /* relayoutVisible */);
final boolean syncRedraw = shouldSendRedrawForSync();
final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers();
final boolean reportDraw = syncRedraw || drawPending;
@@ -3740,14 +3729,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
alwaysConsumeSystemBars, displayId,
syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
- activityToken, activityWindowInfo));
+ mLastReportedActivityWindowInfo));
onResizePostDispatched(drawPending, prevRotation, displayId);
} else {
// TODO(b/301870955): cleanup after launch
try {
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
- syncWithBuffers ? mSyncSeqId : -1, isDragResizing, activityWindowInfo);
+ syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
+ mLastReportedActivityWindowInfo);
onResizePostDispatched(drawPending, prevRotation, displayId);
} catch (RemoteException e) {
// Cancel orientation change of this window to avoid blocking unfreeze display.
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 36758341d4d7..69a88e982f96 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -16,7 +16,6 @@
package com.android.server.permission.access
-import android.permission.flags.Flags
import android.util.Slog
import com.android.modules.utils.BinaryXmlPullParser
import com.android.modules.utils.BinaryXmlSerializer
@@ -79,7 +78,7 @@ private constructor(
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
mutateAppIdPackageNames()
@@ -107,7 +106,7 @@ private constructor(
newState.mutateUserStatesNoWrite()[userId] = MutableUserState()
forEachSchemePolicy { with(it) { onUserAdded(userId) } }
newState.externalState.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
upgradePackageVersion(packageState, userId)
@@ -133,7 +132,7 @@ private constructor(
setPackageStates(packageStates)
setDisabledSystemPackageStates(disabledSystemPackageStates)
packageStates.forEach { (packageName, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
if (packageState.volumeUuid == volumeUuid) {
@@ -161,7 +160,7 @@ private constructor(
with(it) { onStorageVolumeMounted(volumeUuid, packageNames, isSystemUpdated) }
}
packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
if (packageState.volumeUuid == volumeUuid) {
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 63fb468c8f54..87af841b901c 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -81,7 +81,7 @@ class AppIdPermissionPolicy : SchemePolicy() {
override fun MutateStateScope.onUserAdded(userId: Int) {
newState.externalState.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
evaluateAllPermissionStatesForPackageAndUser(packageState, userId, null)
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 44ed3df34f24..65feeb027b3e 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1445,7 +1445,7 @@ class PermissionService(private val service: AccessCheckingService) :
val packageStates = packageManagerLocal.withUnfilteredSnapshot().use { it.packageStates }
service.mutateState {
packageStates.forEach { (packageName, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
val androidPackage = packageState.androidPackage ?: return@forEach
@@ -1880,7 +1880,7 @@ class PermissionService(private val service: AccessCheckingService) :
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
service.mutateState {
snapshot.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
with(policy) { resetRuntimePermissions(packageState.packageName, userId) }
@@ -1925,7 +1925,7 @@ class PermissionService(private val service: AccessCheckingService) :
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
snapshot.packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
val androidPackage = packageState.androidPackage ?: return@forEach
@@ -1943,7 +1943,7 @@ class PermissionService(private val service: AccessCheckingService) :
val permissions = service.getState { with(policy) { getPermissions() } }
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@packageStates
}
val androidPackage = packageState.androidPackage ?: return@packageStates
@@ -2072,7 +2072,7 @@ class PermissionService(private val service: AccessCheckingService) :
val appIdPackageNames = MutableIndexedMap<Int, MutableIndexedSet<String>>()
packageStates.forEach { (_, packageState) ->
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return@forEach
}
appIdPackageNames
@@ -2328,7 +2328,7 @@ class PermissionService(private val service: AccessCheckingService) :
isInstantApp: Boolean,
oldPackage: AndroidPackage?
) {
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return
}
@@ -2358,7 +2358,7 @@ class PermissionService(private val service: AccessCheckingService) :
params: PermissionManagerServiceInternal.PackageInstalledParams,
userId: Int
) {
- if (Flags.ignoreApexPermissions() && androidPackage.isApex) {
+ if (androidPackage.isApex) {
return
}
@@ -2414,7 +2414,7 @@ class PermissionService(private val service: AccessCheckingService) :
sharedUserPkgs: List<AndroidPackage>,
userId: Int
) {
- if (Flags.ignoreApexPermissions() && packageState.isApex) {
+ if (packageState.isApex) {
return
}
diff --git a/services/proguard.flags b/services/proguard.flags
index f84eff79bb15..bf30781b434e 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -29,11 +29,6 @@
public protected *;
}
-# Derivatives of SystemService and other services created via reflection
--keep,allowoptimization,allowaccessmodification class * extends com.android.server.SystemService {
- public <methods>;
-}
-
# Accessed from com.android.compos APEX
-keep,allowoptimization,allowaccessmodification class com.android.internal.art.ArtStatsLog {
public static void write(...);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f08fbde962ef..4a61d32d00b2 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -14011,6 +14011,314 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
+ public void enqueueNotification_acceptsCorrectToken() throws RemoteException {
+ Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("content"))
+ .build();
+ Notification received = parcelAndUnparcel(sent, Notification.CREATOR);
+ assertThat(received.getAllowlistToken()).isEqualTo(
+ NotificationManagerService.ALLOWLIST_TOKEN);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1,
+ parcelAndUnparcel(received, Notification.CREATOR), mUserId);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken())
+ .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
+ public void enqueueNotification_acceptsNullToken_andPopulatesIt() throws RemoteException {
+ Notification receivedWithoutParceling = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("content"))
+ .build();
+ assertThat(receivedWithoutParceling.getAllowlistToken()).isNull();
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1,
+ parcelAndUnparcel(receivedWithoutParceling, Notification.CREATOR), mUserId);
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).hasSize(1);
+ assertThat(mService.mNotificationList.get(0).getNotification().getAllowlistToken())
+ .isEqualTo(NotificationManagerService.ALLOWLIST_TOKEN);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
+ public void enqueueNotification_rejectsOtherToken() throws RemoteException {
+ Notification sent = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("content"))
+ .build();
+ sent.overrideAllowlistToken(new Binder());
+ Notification received = parcelAndUnparcel(sent, Notification.CREATOR);
+ assertThat(received.getAllowlistToken()).isEqualTo(sent.getAllowlistToken());
+
+ assertThrows(SecurityException.class, () ->
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1,
+ parcelAndUnparcel(received, Notification.CREATOR), mUserId));
+ waitForIdle();
+
+ assertThat(mService.mNotificationList).isEmpty();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
+ public void enqueueNotification_customParcelingWithFakeInnerToken_hasCorrectTokenInIntents()
+ throws RemoteException {
+ Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("content"))
+ .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("public"))
+ .build())
+ .build();
+ sentFromApp.publicVersion.overrideAllowlistToken(new Binder());
+
+ // Instead of using the normal parceling, assume the caller parcels it by hand, including a
+ // null token in the outer notification (as would be expected, and as is verified by
+ // enqueue) but trying to sneak in a different one in the inner notification, hoping it gets
+ // propagated to the PendingIntents.
+ Parcel parcelSentFromApp = Parcel.obtain();
+ writeNotificationToParcelCustom(parcelSentFromApp, sentFromApp, new ArraySet<>(
+ Lists.newArrayList(sentFromApp.contentIntent,
+ sentFromApp.publicVersion.contentIntent)));
+
+ // Use the unparceling as received in enqueueNotificationWithTag()
+ parcelSentFromApp.setDataPosition(0);
+ Notification receivedByNms = new Notification(parcelSentFromApp);
+
+ // Verify that all the pendingIntents have the correct token.
+ assertThat(receivedByNms.contentIntent.getWhitelistToken()).isEqualTo(
+ NotificationManagerService.ALLOWLIST_TOKEN);
+ assertThat(receivedByNms.publicVersion.contentIntent.getWhitelistToken()).isEqualTo(
+ NotificationManagerService.ALLOWLIST_TOKEN);
+ }
+
+ /**
+ * Replicates the behavior of {@link Notification#writeToParcel} but excluding the
+ * "always use the same allowlist token as the root notification" parts.
+ */
+ private static void writeNotificationToParcelCustom(Parcel parcel, Notification notif,
+ ArraySet<PendingIntent> allPendingIntents) {
+ int flags = 0;
+ parcel.writeInt(1); // version?
+
+ parcel.writeStrongBinder(notif.getAllowlistToken());
+ parcel.writeLong(notif.when);
+ parcel.writeLong(notif.creationTime);
+ if (notif.getSmallIcon() != null) {
+ parcel.writeInt(1);
+ notif.getSmallIcon().writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeInt(notif.number);
+ if (notif.contentIntent != null) {
+ parcel.writeInt(1);
+ notif.contentIntent.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ if (notif.deleteIntent != null) {
+ parcel.writeInt(1);
+ notif.deleteIntent.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ if (notif.tickerText != null) {
+ parcel.writeInt(1);
+ TextUtils.writeToParcel(notif.tickerText, parcel, flags);
+ } else {
+ parcel.writeInt(0);
+ }
+ if (notif.tickerView != null) {
+ parcel.writeInt(1);
+ notif.tickerView.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ if (notif.contentView != null) {
+ parcel.writeInt(1);
+ notif.contentView.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ if (notif.getLargeIcon() != null) {
+ parcel.writeInt(1);
+ notif.getLargeIcon().writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeInt(notif.defaults);
+ parcel.writeInt(notif.flags);
+
+ if (notif.sound != null) {
+ parcel.writeInt(1);
+ notif.sound.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeInt(notif.audioStreamType);
+
+ if (notif.audioAttributes != null) {
+ parcel.writeInt(1);
+ notif.audioAttributes.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeLongArray(notif.vibrate);
+ parcel.writeInt(notif.ledARGB);
+ parcel.writeInt(notif.ledOnMS);
+ parcel.writeInt(notif.ledOffMS);
+ parcel.writeInt(notif.iconLevel);
+
+ if (notif.fullScreenIntent != null) {
+ parcel.writeInt(1);
+ notif.fullScreenIntent.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeInt(notif.priority);
+
+ parcel.writeString8(notif.category);
+
+ parcel.writeString8(notif.getGroup());
+
+ parcel.writeString8(notif.getSortKey());
+
+ parcel.writeBundle(notif.extras); // null ok
+
+ parcel.writeTypedArray(notif.actions, 0); // null ok
+
+ if (notif.bigContentView != null) {
+ parcel.writeInt(1);
+ notif.bigContentView.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ if (notif.headsUpContentView != null) {
+ parcel.writeInt(1);
+ notif.headsUpContentView.writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeInt(notif.visibility);
+
+ if (notif.publicVersion != null) {
+ parcel.writeInt(1);
+ writeNotificationToParcelCustom(parcel, notif.publicVersion, new ArraySet<>());
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeInt(notif.color);
+
+ if (notif.getChannelId() != null) {
+ parcel.writeInt(1);
+ parcel.writeString8(notif.getChannelId());
+ } else {
+ parcel.writeInt(0);
+ }
+ parcel.writeLong(notif.getTimeoutAfter());
+
+ if (notif.getShortcutId() != null) {
+ parcel.writeInt(1);
+ parcel.writeString8(notif.getShortcutId());
+ } else {
+ parcel.writeInt(0);
+ }
+
+ if (notif.getLocusId() != null) {
+ parcel.writeInt(1);
+ notif.getLocusId().writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeInt(notif.getBadgeIconType());
+
+ if (notif.getSettingsText() != null) {
+ parcel.writeInt(1);
+ TextUtils.writeToParcel(notif.getSettingsText(), parcel, flags);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeInt(notif.getGroupAlertBehavior());
+
+ if (notif.getBubbleMetadata() != null) {
+ parcel.writeInt(1);
+ notif.getBubbleMetadata().writeToParcel(parcel, 0);
+ } else {
+ parcel.writeInt(0);
+ }
+
+ parcel.writeBoolean(notif.getAllowSystemGeneratedContextualActions());
+
+ parcel.writeInt(Notification.FOREGROUND_SERVICE_DEFAULT); // no getter for mFgsDeferBehavior
+
+ // mUsesStandardHeader is not written because it should be recomputed in listeners
+
+ parcel.writeArraySet(allPendingIntents);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ @EnableFlags(android.app.Flags.FLAG_SECURE_ALLOWLIST_TOKEN)
+ public void getActiveNotifications_doesNotLeakAllowlistToken() throws RemoteException {
+ Notification sentFromApp = new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("content"))
+ .setPublicVersion(new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("public"))
+ .build())
+ .extend(new Notification.WearableExtender()
+ .addPage(new Notification.Builder(mContext, TEST_CHANNEL_ID)
+ .setContentIntent(createPendingIntent("wearPage"))
+ .build()))
+ .build();
+ // Binder transition: app -> NMS
+ Notification receivedByNms = parcelAndUnparcel(sentFromApp, Notification.CREATOR);
+ assertThat(receivedByNms.getAllowlistToken()).isEqualTo(
+ NotificationManagerService.ALLOWLIST_TOKEN);
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 1,
+ parcelAndUnparcel(receivedByNms, Notification.CREATOR), mUserId);
+ waitForIdle();
+ assertThat(mService.mNotificationList).hasSize(1);
+ Notification posted = mService.mNotificationList.get(0).getNotification();
+ assertThat(posted.getAllowlistToken()).isEqualTo(
+ NotificationManagerService.ALLOWLIST_TOKEN);
+ assertThat(posted.contentIntent.getWhitelistToken()).isEqualTo(
+ NotificationManagerService.ALLOWLIST_TOKEN);
+
+ ParceledListSlice<StatusBarNotification> listSentFromNms =
+ mBinderService.getAppActiveNotifications(mPkg, mUserId);
+ // Binder transition: NMS -> app. App doesn't have the allowlist token so clear it
+ // (having a different one would produce the same effect; the relevant thing is to not let
+ // out ALLOWLIST_TOKEN).
+ // Note: for other tests, this is restored by constructing TestableNMS in setup().
+ Notification.processAllowlistToken = null;
+ ParceledListSlice<StatusBarNotification> listReceivedByApp = parcelAndUnparcel(
+ listSentFromNms, ParceledListSlice.CREATOR);
+ Notification gottenBackByApp = listReceivedByApp.getList().get(0).getNotification();
+
+ assertThat(gottenBackByApp.getAllowlistToken()).isNull();
+ assertThat(gottenBackByApp.contentIntent.getWhitelistToken()).isNull();
+ assertThat(gottenBackByApp.publicVersion.getAllowlistToken()).isNull();
+ assertThat(gottenBackByApp.publicVersion.contentIntent.getWhitelistToken()).isNull();
+ assertThat(new Notification.WearableExtender(gottenBackByApp).getPages()
+ .get(0).getAllowlistToken()).isNull();
+ assertThat(new Notification.WearableExtender(gottenBackByApp).getPages()
+ .get(0).contentIntent.getWhitelistToken()).isNull();
+ }
+
+ @Test
public void enqueueNotification_allowlistsPendingIntents() throws RemoteException {
PendingIntent contentIntent = createPendingIntent("content");
PendingIntent actionIntent1 = createPendingIntent("action1");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 018600641853..42fe3a747b64 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1630,6 +1630,7 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(controller.mWaitingTransitions.contains(transition));
assertTrue(controller.isTransientHide(appTask));
assertTrue(controller.isTransientVisible(appTask));
+ assertTrue(controller.isTransientLaunch(recent));
}
@Test
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
index 92b865542257..063088d54b45 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt
@@ -50,6 +50,9 @@ class ShowImeOnAppStartWhenLaunchingAppFromOverviewTest(flicker: LegacyFlickerTe
testApp.launchViaIntent(wmHelper)
testApp.openIME(wmHelper)
this.setRotation(flicker.scenario.startRotation)
+ if (flicker.scenario.isTablet) {
+ tapl.launchedAppState.swipeUpToUnstashTaskbar()
+ }
tapl.launchedAppState.switchToOverview()
wmHelper.StateSyncBuilder().withRecentsActivityVisible().waitForAndVerify()
}