summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt3
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/content/pm/ActivityInfo.java28
-rw-r--r--core/java/android/service/dreams/DreamActivity.java10
-rw-r--r--core/java/android/service/dreams/DreamService.java4
-rw-r--r--core/java/android/view/View.java12
-rw-r--r--core/java/android/view/ViewRootImpl.java81
-rw-r--r--core/java/android/view/WindowManager.java36
-rw-r--r--core/java/com/android/internal/app/procstats/ProcessState.java70
-rw-r--r--core/java/com/android/internal/app/procstats/ProcessStats.java76
-rw-r--r--core/java/com/android/internal/app/procstats/StatsEventOutput.java98
-rw-r--r--core/res/res/layout/transient_notification.xml2
-rw-r--r--core/res/res/layout/transient_notification_with_icon.xml2
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java159
-rw-r--r--data/etc/privapp-permissions-platform.xml6
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java129
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java208
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java270
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt92
-rw-r--r--media/java/android/media/projection/OWNERS2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt26
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt5
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt1
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt35
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt164
-rw-r--r--packages/SystemUI/res-keyguard/values-land/dimens.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/drawable/ic_keyboard_backlight.xml12
-rw-r--r--packages/SystemUI/res/values-land/dimens.xml4
-rw-r--r--packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/colors.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml18
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt16
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java28
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt10
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt18
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt38
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java25
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt101
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt252
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt176
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt231
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt151
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt163
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt140
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt146
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt)39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt130
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt87
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt227
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt144
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt62
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java132
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt213
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt226
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt173
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java17
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java8
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java18
-rw-r--r--services/core/java/com/android/server/display/BrightnessSetting.java17
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java81
-rw-r--r--services/core/java/com/android/server/display/PersistentDataStore.java27
-rw-r--r--services/core/java/com/android/server/media/projection/OWNERS3
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java101
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java9
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java161
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java98
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/EventLogTags.logtags6
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java15
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java20
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/people/java/com/android/server/people/data/ContactsQueryHelper.java2
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java27
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java28
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java178
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java25
212 files changed, 6690 insertions, 1997 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index c0e89d2c4a05..14bf5320a438 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -797,6 +797,7 @@ package android.content.pm {
field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -2917,7 +2918,9 @@ package android.view {
}
@UiThread public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback {
+ method public void getBoundsOnScreen(@NonNull android.graphics.Rect, boolean);
method public android.view.View getTooltipView();
+ method public void getWindowDisplayFrame(@NonNull android.graphics.Rect);
method public boolean isAutofilled();
method public static boolean isDefaultFocusHighlightEnabled();
method public boolean isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0fd80c5698a9..7f48e39a9a79 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6965,8 +6965,10 @@ public class Notification implements Parcelable
/**
* Returns whether an app can colorize due to the android.permission.USE_COLORIZED_NOTIFICATIONS
* permission. The permission is checked when a notification is enqueued.
+ *
+ * @hide
*/
- private boolean hasColorizedPermission() {
+ public boolean hasColorizedPermission() {
return (flags & Notification.FLAG_CAN_COLORIZE) != 0;
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index bbe99f59fc21..226278cbe44d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1104,6 +1104,34 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
264301586L; // buganizer id
/**
+ * This change id forces the packages it is applied to sandbox {@link android.view.View} API to
+ * an activity bounds for:
+ *
+ * <p>{@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View}#getWindowDisplayFrame,
+ * {@link android.view.View}#getBoundsOnScreen.
+ *
+ * <p>For {@link android.view.View#getWindowVisibleDisplayFrame} and
+ * {@link android.view.View}#getWindowDisplayFrame this sandboxing is happening indirectly
+ * through
+ * {@link android.view.ViewRootImpl}#getWindowVisibleDisplayFrame,
+ * {@link android.view.ViewRootImpl}#getDisplayFrame respectively.
+ *
+ * <p>Some applications assume that they occupy the whole screen and therefore use the display
+ * coordinates in their calculations as if an activity is positioned in the top-left corner of
+ * the screen, with left coordinate equal to 0. This may not be the case of applications in
+ * multi-window and in letterbox modes. This can lead to shifted or out of bounds UI elements in
+ * case the activity is Letterboxed or is in multi-window mode.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // buganizer id
+
+ /**
* This change id is the gatekeeper for all treatments that force a given min aspect ratio.
* Enabling this change will allow the following min aspect ratio treatments to be applied:
* OVERRIDE_MIN_ASPECT_RATIO_MEDIUM
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index a3892238f1e6..a2fa1392b079 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -58,13 +58,11 @@ public class DreamActivity extends Activity {
setTitle(title);
}
- final Object callback = getIntent().getExtras().getBinder(EXTRA_CALLBACK);
- if (callback instanceof DreamService.DreamActivityCallbacks) {
- mCallback = (DreamService.DreamActivityCallbacks) callback;
+ final Bundle extras = getIntent().getExtras();
+ mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
+
+ if (mCallback != null) {
mCallback.onActivityCreated(this);
- } else {
- mCallback = null;
- finishAndRemoveTask();
}
}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index d79ea8929047..224307297e76 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1409,6 +1409,10 @@ public class DreamService extends Service implements Window.Callback {
// Request the DreamOverlay be told to dream with dream's window
// parameters once the window has been attached.
mDreamStartOverlayConsumer = overlay -> {
+ if (mWindow == null) {
+ Slog.d(TAG, "mWindow is null");
+ return;
+ }
try {
overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
mDreamComponent.flattenToString(),
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d7480e5037f4..543a22b0f4d0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8774,7 +8774,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@UnsupportedAppUsage
- public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ @TestApi
+ public void getBoundsOnScreen(@NonNull Rect outRect, boolean clipToParent) {
if (mAttachInfo == null) {
return;
}
@@ -8782,6 +8783,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
getBoundsToScreenInternal(position, clipToParent);
outRect.set(Math.round(position.left), Math.round(position.top),
Math.round(position.right), Math.round(position.bottom));
+ // If "Sandboxing View Bounds APIs" override is enabled, applyViewBoundsSandboxingIfNeeded
+ // will sandbox outRect within window bounds.
+ mAttachInfo.mViewRootImpl.applyViewBoundsSandboxingIfNeeded(outRect);
}
/**
@@ -15586,7 +15590,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @hide
*/
@UnsupportedAppUsage
- public void getWindowDisplayFrame(Rect outRect) {
+ @TestApi
+ public void getWindowDisplayFrame(@NonNull Rect outRect) {
if (mAttachInfo != null) {
mAttachInfo.mViewRootImpl.getDisplayFrame(outRect);
return;
@@ -25786,6 +25791,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (info != null) {
outLocation[0] += info.mWindowLeft;
outLocation[1] += info.mWindowTop;
+ // If OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS override is enabled,
+ // applyViewLocationSandboxingIfNeeded sandboxes outLocation within window bounds.
+ info.mViewRootImpl.applyViewLocationSandboxingIfNeeded(outLocation);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f9e84114a7da..f6211fd488d8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -16,6 +16,7 @@
package android.view;
+import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
@@ -82,6 +83,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -94,12 +96,14 @@ import android.animation.LayoutTransition;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Size;
import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ICompatCameraControlCallback;
import android.app.ResourcesManager;
import android.app.WindowConfiguration;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -871,6 +875,15 @@ public final class ViewRootImpl implements ViewParent,
private boolean mRelayoutRequested;
+ /**
+ * Whether sandboxing of {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getLocationOnScreen(int[])},
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}
+ * within Activity bounds is enabled for the current application.
+ */
+ private final boolean mViewBoundsSandboxingEnabled;
+
private int mLastTransformHint = Integer.MIN_VALUE;
/**
@@ -958,6 +971,8 @@ public final class ViewRootImpl implements ViewParent,
mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -8426,6 +8441,9 @@ public final class ViewRootImpl implements ViewParent,
*/
void getDisplayFrame(Rect outFrame) {
outFrame.set(mTmpFrames.displayFrame);
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
}
/**
@@ -8442,6 +8460,69 @@ public final class ViewRootImpl implements ViewParent,
outFrame.top += insets.top;
outFrame.right -= insets.right;
outFrame.bottom -= insets.bottom;
+ // Apply sandboxing here (in getter) due to possible layout updates on the client after
+ // mTmpFrames.displayFrame is received from the server.
+ applyViewBoundsSandboxingIfNeeded(outFrame);
+ }
+
+ /**
+ * Offset outRect to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.ViewRootImpl#getDisplayFrame} and
+ * {@link android.view.ViewRootImpl#getWindowVisibleDisplayFrame}, which are invoked by
+ * {@link android.view.View#getWindowDisplayFrame} and
+ * {@link android.view.View#getWindowVisibleDisplayFrame}, as well as
+ * {@link android.view.ViewDebug#captureLayers} for debugging.
+ */
+ void applyViewBoundsSandboxingIfNeeded(final Rect inOutRect) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ inOutRect.offset(-bounds.left, -bounds.top);
+ }
+ }
+
+ /**
+ * Offset outLocation to make it sandboxed within Window's bounds.
+ *
+ * <p>This is used by {@link android.view.View#getLocationOnScreen(int[])}
+ */
+ public void applyViewLocationSandboxingIfNeeded(@Size(2) int[] outLocation) {
+ if (mViewBoundsSandboxingEnabled) {
+ final Rect bounds = getConfiguration().windowConfiguration.getBounds();
+ outLocation[0] -= bounds.left;
+ outLocation[1] -= bounds.top;
+ }
+ }
+
+ private boolean getViewBoundsSandboxingEnabled() {
+ // System dialogs (e.g. ANR) can be created within System process, so handleBindApplication
+ // may be never called. This results into all app compat changes being enabled
+ // (see b/268007823) because AppCompatCallbacks.install() is never called with non-empty
+ // array.
+ // With ActivityThread.isSystem we verify that it is not the system process,
+ // then this CompatChange can take effect.
+ if (ActivityThread.isSystem()
+ || !CompatChanges.isChangeEnabled(OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS)) {
+ // It is a system process or OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS change-id is disabled.
+ return false;
+ }
+
+ // OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is enabled by the device manufacturer.
+ try {
+ final List<PackageManager.Property> properties = mContext.getPackageManager()
+ .queryApplicationProperty(PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS);
+
+ final boolean isOptedOut = !properties.isEmpty() && !properties.get(0).getBoolean();
+ if (isOptedOut) {
+ // PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS is disabled by the app devs.
+ return false;
+ }
+ } catch (RuntimeException e) {
+ // remote exception.
+ }
+
+ return true;
}
/**
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 17df585e424a..a01c8328b37c 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -854,6 +854,42 @@ public interface WindowManager extends ViewManager {
/**
* Application level {@link android.content.pm.PackageManager.Property PackageManager
+ * .Property} for an app to inform the system that it needs to be opted-out from the
+ * compatibility treatment that sandboxes {@link android.view.View} API.
+ *
+ * <p>The treatment can be enabled by device manufacturers for applications which misuse
+ * {@link android.view.View} APIs by expecting that
+ * {@link android.view.View#getLocationOnScreen},
+ * {@link android.view.View#getBoundsOnScreen},
+ * {@link android.view.View#getWindowVisibleDisplayFrame},
+ * {@link android.view.View#getWindowDisplayFrame}
+ * return coordinates as if an activity is positioned in the top-left corner of the screen, with
+ * left coordinate equal to 0. This may not be the case for applications in multi-window and in
+ * letterbox modes.
+ *
+ * <p>Setting this property to {@code false} informs the system that the application must be
+ * opted-out from the "Sandbox {@link android.view.View} API to Activity bounds" treatment even
+ * if the device manufacturer has opted the app into the treatment.
+ *
+ * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS"
+ * android:value="false"/&gt;
+ * &lt;/application&gt;
+ * </pre>
+ *
+ * @hide
+ */
+ // TODO(b/263984287): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
+ "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
+
+ /**
+ * Application level {@link android.content.pm.PackageManager.Property PackageManager
* .Property} for an app to inform the system that the application can be opted-in or opted-out
* from the compatibility treatment that enables sending a fake focus event for unfocused
* resumed split screen activities. This is needed because some game engines wait to get
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 72b9cd272d02..818a50366115 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -73,6 +73,7 @@ import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection;
import java.io.PrintWriter;
import java.util.Comparator;
+import java.util.concurrent.TimeUnit;
public final class ProcessState {
private static final String TAG = "ProcessStats";
@@ -1542,6 +1543,75 @@ public final class ProcessState {
proto.write(fieldId, procName);
}
+ /** Dumps the duration of each state to statsEventOutput. */
+ public void dumpStateDurationToStatsd(
+ int atomTag, ProcessStats processStats, StatsEventOutput statsEventOutput) {
+ long topMs = 0;
+ long fgsMs = 0;
+ long boundTopMs = 0;
+ long boundFgsMs = 0;
+ long importantForegroundMs = 0;
+ long cachedMs = 0;
+ long frozenMs = 0;
+ long otherMs = 0;
+ for (int i = 0, size = mDurations.getKeyCount(); i < size; i++) {
+ final int key = mDurations.getKeyAt(i);
+ final int type = SparseMappingTable.getIdFromKey(key);
+ int procStateIndex = type % STATE_COUNT;
+ long duration = mDurations.getValue(key);
+ switch (procStateIndex) {
+ case STATE_TOP:
+ topMs += duration;
+ break;
+ case STATE_BOUND_TOP_OR_FGS:
+ boundTopMs += duration;
+ break;
+ case STATE_FGS:
+ fgsMs += duration;
+ break;
+ case STATE_IMPORTANT_FOREGROUND:
+ case STATE_IMPORTANT_BACKGROUND:
+ importantForegroundMs += duration;
+ break;
+ case STATE_BACKUP:
+ case STATE_SERVICE:
+ case STATE_SERVICE_RESTARTING:
+ case STATE_RECEIVER:
+ case STATE_HEAVY_WEIGHT:
+ case STATE_HOME:
+ case STATE_LAST_ACTIVITY:
+ case STATE_PERSISTENT:
+ otherMs += duration;
+ break;
+ case STATE_CACHED_ACTIVITY:
+ case STATE_CACHED_ACTIVITY_CLIENT:
+ case STATE_CACHED_EMPTY:
+ cachedMs += duration;
+ break;
+ // TODO (b/261910877) Add support for tracking boundFgsMs and
+ // frozenMs.
+ }
+ }
+ statsEventOutput.write(
+ atomTag,
+ getUid(),
+ getName(),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ processStats.mTimePeriodEndUptime
+ - processStats.mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(topMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(fgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundTopMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(boundFgsMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(importantForegroundMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(cachedMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(frozenMs),
+ (int) TimeUnit.MILLISECONDS.toSeconds(otherMs));
+ }
+
/** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */
public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto, long fieldId,
String procName, int uid, long now,
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index d2b2f0a2b894..f3ed09a861e3 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -43,6 +43,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.AssociationState.SourceKey;
import com.android.internal.app.procstats.AssociationState.SourceState;
+import com.android.internal.util.function.QuintConsumer;
import dalvik.system.VMRuntime;
@@ -56,6 +57,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -2389,6 +2392,79 @@ public final class ProcessStats implements Parcelable {
}
}
+ void forEachProcess(Consumer<ProcessState> consumer) {
+ final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+ for (int ip = 0, size = procMap.size(); ip < size; ip++) {
+ final SparseArray<ProcessState> uids = procMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final ProcessState processState = uids.valueAt(iu);
+ consumer.accept(processState);
+ }
+ }
+ }
+
+ void forEachAssociation(
+ QuintConsumer<AssociationState, Integer, String, SourceKey, SourceState> consumer) {
+ final ArrayMap<String, SparseArray<LongSparseArray<PackageState>>> pkgMap =
+ mPackages.getMap();
+ for (int ip = 0, size = pkgMap.size(); ip < size; ip++) {
+ final SparseArray<LongSparseArray<PackageState>> uids = pkgMap.valueAt(ip);
+ for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
+ final int uid = uids.keyAt(iu);
+ final LongSparseArray<PackageState> versions = uids.valueAt(iu);
+ for (int iv = 0, versionsSize = versions.size(); iv < versionsSize; iv++) {
+ final PackageState state = versions.valueAt(iv);
+ for (int iasc = 0, ascSize = state.mAssociations.size();
+ iasc < ascSize;
+ iasc++) {
+ final String serviceName = state.mAssociations.keyAt(iasc);
+ final AssociationState asc = state.mAssociations.valueAt(iasc);
+ for (int is = 0, sourcesSize = asc.mSources.size();
+ is < sourcesSize;
+ is++) {
+ final SourceState src = asc.mSources.valueAt(is);
+ final SourceKey key = asc.mSources.keyAt(is);
+ consumer.accept(asc, uid, serviceName, key, src);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /** Dumps the stats of all processes to statsEventOutput. */
+ public void dumpProcessState(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachProcess(
+ (processState) -> {
+ if (processState.isMultiPackage()
+ && processState.getCommonProcess() != processState) {
+ return;
+ }
+ processState.dumpStateDurationToStatsd(atomTag, this, statsEventOutput);
+ });
+ }
+
+ /** Dumps all process association data to statsEventOutput. */
+ public void dumpProcessAssociation(int atomTag, StatsEventOutput statsEventOutput) {
+ forEachAssociation(
+ (asc, serviceUid, serviceName, key, src) -> {
+ statsEventOutput.write(
+ atomTag,
+ key.mUid,
+ key.mProcess,
+ serviceUid,
+ serviceName,
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodEndUptime),
+ (int)
+ TimeUnit.MILLISECONDS.toSeconds(
+ mTimePeriodEndUptime - mTimePeriodStartUptime),
+ (int) TimeUnit.MILLISECONDS.toSeconds(src.mDuration),
+ src.mActiveCount,
+ asc.getProcessName());
+ });
+ }
+
private void dumpProtoPreamble(ProtoOutputStream proto) {
proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
diff --git a/core/java/com/android/internal/app/procstats/StatsEventOutput.java b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
new file mode 100644
index 000000000000..b2e405475a4e
--- /dev/null
+++ b/core/java/com/android/internal/app/procstats/StatsEventOutput.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.procstats;
+
+import android.util.StatsEvent;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+
+/**
+ * A simple wrapper of FrameworkStatsLog.buildStatsEvent. This allows unit tests to mock out the
+ * dependency.
+ */
+public class StatsEventOutput {
+
+ List<StatsEvent> mOutput;
+
+ public StatsEventOutput(List<StatsEvent> output) {
+ mOutput = output;
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int uid,
+ String processName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int topSeconds,
+ int fgsSeconds,
+ int boundTopSeconds,
+ int boundFgsSeconds,
+ int importantForegroundSeconds,
+ int cachedSeconds,
+ int frozenSeconds,
+ int otherSeconds) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ uid,
+ processName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ topSeconds,
+ fgsSeconds,
+ boundTopSeconds,
+ boundFgsSeconds,
+ importantForegroundSeconds,
+ cachedSeconds,
+ frozenSeconds,
+ otherSeconds));
+ }
+
+ /** Writes the data to the output. */
+ public void write(
+ int atomTag,
+ int clientUid,
+ String processName,
+ int serviceUid,
+ String serviceName,
+ int measurementStartUptimeSecs,
+ int measurementEndUptimeSecs,
+ int measurementDurationUptimeSecs,
+ int activeDurationUptimeSecs,
+ int activeCount,
+ String serviceProcessName) {
+ mOutput.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ clientUid,
+ processName,
+ serviceUid,
+ serviceName,
+ measurementStartUptimeSecs,
+ measurementEndUptimeSecs,
+ measurementDurationUptimeSecs,
+ activeDurationUptimeSecs,
+ activeCount,
+ serviceProcessName));
+ }
+}
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 3259201f6e9b..8bedb897dc19 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -25,7 +25,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:maxWidth="@dimen/toast_width"
- android:background="?android:attr/toastFrameBackground"
+ android:background="?android:attr/colorBackground"
android:elevation="@dimen/toast_elevation"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
diff --git a/core/res/res/layout/transient_notification_with_icon.xml b/core/res/res/layout/transient_notification_with_icon.xml
index e9b17df75333..0dfb3adc8364 100644
--- a/core/res/res/layout/transient_notification_with_icon.xml
+++ b/core/res/res/layout/transient_notification_with_icon.xml
@@ -22,7 +22,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:maxWidth="@dimen/toast_width"
- android:background="?android:attr/toastFrameBackground"
+ android:background="?android:attr/colorBackground"
android:elevation="@dimen/toast_elevation"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dafa0ad7989f..993fcf877055 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6092,4 +6092,8 @@
<!-- Whether to show weather on the lock screen by default. -->
<bool name="config_lockscreenWeatherEnabledByDefault">false</bool>
+
+ <!-- Whether we should persist the brightness value in nits for the default display even if
+ the underlying display device changes. -->
+ <bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 591ba5feeee9..c04c322e2d87 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2282,6 +2282,7 @@
<java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
<java-symbol type="array" name="config_nonPreemptibleInputMethods" />
<java-symbol type="bool" name="config_enhancedConfirmationModeEnabled" />
+ <java-symbol type="bool" name="config_persistBrightnessNitsForDefaultDisplay" />
<java-symbol type="layout" name="resolver_list" />
<java-symbol type="id" name="resolver_list" />
diff --git a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
new file mode 100644
index 000000000000..9b9a84b79da3
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app.procstats;
+
+import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import junit.framework.TestCase;
+
+import org.junit.Before;
+import org.mockito.Mock;
+
+import java.util.concurrent.TimeUnit;
+
+/** Provides test cases for ProcessStats. */
+public class ProcessStatsTest extends TestCase {
+
+ private static final String APP_1_PACKAGE_NAME = "com.android.testapp";
+ private static final int APP_1_UID = 5001;
+ private static final long APP_1_VERSION = 10;
+ private static final String APP_1_PROCESS_NAME = "com.android.testapp.p";
+ private static final String APP_1_SERVICE_NAME = "com.android.testapp.service";
+
+ private static final String APP_2_PACKAGE_NAME = "com.android.testapp2";
+ private static final int APP_2_UID = 5002;
+ private static final long APP_2_VERSION = 30;
+ private static final String APP_2_PROCESS_NAME = "com.android.testapp2.p";
+
+ private static final long NOW_MS = 123000;
+ private static final int DURATION_SECS = 6;
+
+ @Mock StatsEventOutput mStatsEventOutput;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ }
+
+ @SmallTest
+ public void testDumpProcessState() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processStats.getProcessStateLocked(
+ APP_2_PACKAGE_NAME, APP_2_UID, APP_2_VERSION, APP_2_PROCESS_NAME);
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testNonZeroProcessStateDuration() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ ProcessState processState =
+ processStats.getProcessStateLocked(
+ APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
+ processState.setCombinedState(STATE_TOP, NOW_MS);
+ processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
+ processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_STATE),
+ eq(APP_1_UID),
+ eq(APP_1_PROCESS_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(DURATION_SECS),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(0));
+ }
+
+ @SmallTest
+ public void testDumpProcessAssociation() throws Exception {
+ ProcessStats processStats = new ProcessStats();
+ AssociationState associationState =
+ processStats.getAssociationStateLocked(
+ APP_1_PACKAGE_NAME,
+ APP_1_UID,
+ APP_1_VERSION,
+ APP_1_PROCESS_NAME,
+ APP_1_SERVICE_NAME);
+ AssociationState.SourceState sourceState =
+ associationState.startSource(APP_2_UID, APP_2_PROCESS_NAME, APP_2_PACKAGE_NAME);
+ sourceState.stop();
+ processStats.dumpProcessAssociation(
+ FrameworkStatsLog.PROCESS_ASSOCIATION, mStatsEventOutput);
+ verify(mStatsEventOutput)
+ .write(
+ eq(FrameworkStatsLog.PROCESS_ASSOCIATION),
+ eq(APP_2_UID),
+ eq(APP_2_PROCESS_NAME),
+ eq(APP_1_UID),
+ eq(APP_1_SERVICE_NAME),
+ anyInt(),
+ anyInt(),
+ eq(0),
+ eq(0),
+ eq(0),
+ eq(APP_1_PROCESS_NAME));
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index caa118a409f5..e1c9b3c121f8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -517,6 +517,12 @@ applications that come with the platform
<permission name="android.permission.BIND_WALLPAPER"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.wallpaper">
+ <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+ <permission name="android.permission.BIND_WALLPAPER"/>
+ <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.dynsystem">
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 1cf819af7a24..3d7fb16bb846 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1051,6 +1051,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/WindowContainer.java"
},
+ "-1104347731": {
+ "message": "Setting requested orientation %s for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-1103716954": {
"message": "Not removing %s due to exit animation",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index c08085ee8cd0..541c0f04b9b9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -135,6 +135,30 @@ public class BubbleController implements ConfigurationChangeListener {
private static final boolean BUBBLE_BAR_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+
+ /**
+ * Common interface to send updates to bubble views.
+ */
+ public interface BubbleViewCallback {
+ /** Called when the provided bubble should be removed. */
+ void removeBubble(Bubble removedBubble);
+ /** Called when the provided bubble should be added. */
+ void addBubble(Bubble addedBubble);
+ /** Called when the provided bubble should be updated. */
+ void updateBubble(Bubble updatedBubble);
+ /** Called when the provided bubble should be selected. */
+ void selectionChanged(BubbleViewProvider selectedBubble);
+ /** Called when the provided bubble's suppression state has changed. */
+ void suppressionChanged(Bubble bubble, boolean isSuppressed);
+ /** Called when the expansion state of bubbles has changed. */
+ void expansionChanged(boolean isExpanded);
+ /**
+ * Called when the order of the bubble list has changed. Depending on the expanded state
+ * the pointer might need to be updated.
+ */
+ void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer);
+ }
+
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
@@ -157,7 +181,6 @@ public class BubbleController implements ConfigurationChangeListener {
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
-
private final ShellExecutor mBackgroundExecutor;
private BubbleLogger mLogger;
@@ -1285,6 +1308,58 @@ public class BubbleController implements ConfigurationChangeListener {
});
}
+ private final BubbleViewCallback mBubbleViewCallback = new BubbleViewCallback() {
+ @Override
+ public void removeBubble(Bubble removedBubble) {
+ if (mStackView != null) {
+ mStackView.removeBubble(removedBubble);
+ }
+ }
+
+ @Override
+ public void addBubble(Bubble addedBubble) {
+ if (mStackView != null) {
+ mStackView.addBubble(addedBubble);
+ }
+ }
+
+ @Override
+ public void updateBubble(Bubble updatedBubble) {
+ if (mStackView != null) {
+ mStackView.updateBubble(updatedBubble);
+ }
+ }
+
+ @Override
+ public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) {
+ if (mStackView != null) {
+ mStackView.updateBubbleOrder(bubbleOrder, updatePointer);
+ }
+ }
+
+ @Override
+ public void suppressionChanged(Bubble bubble, boolean isSuppressed) {
+ if (mStackView != null) {
+ mStackView.setBubbleSuppressed(bubble, isSuppressed);
+ }
+ }
+
+ @Override
+ public void expansionChanged(boolean isExpanded) {
+ if (mStackView != null) {
+ mStackView.setExpanded(isExpanded);
+ }
+ }
+
+ @Override
+ public void selectionChanged(BubbleViewProvider selectedBubble) {
+ if (mStackView != null) {
+ mStackView.setSelectedBubble(selectedBubble);
+ }
+
+ }
+ };
+
@SuppressWarnings("FieldCanBeLocal")
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
@@ -1307,7 +1382,8 @@ public class BubbleController implements ConfigurationChangeListener {
// Lazy load overflow bubbles from disk
loadOverflowBubblesFromDisk();
- mStackView.updateOverflowButtonDot();
+ // If bubbles in the overflow have a dot, make sure the overflow shows a dot
+ updateOverflowButtonDot();
// Update bubbles in overflow.
if (mOverflowListener != null) {
@@ -1322,9 +1398,7 @@ public class BubbleController implements ConfigurationChangeListener {
final Bubble bubble = removed.first;
@Bubbles.DismissReason final int reason = removed.second;
- if (mStackView != null) {
- mStackView.removeBubble(bubble);
- }
+ mBubbleViewCallback.removeBubble(bubble);
// Leave the notification in place if we're dismissing due to user switching, or
// because DND is suppressing the bubble. In both of those cases, we need to be able
@@ -1354,49 +1428,47 @@ public class BubbleController implements ConfigurationChangeListener {
}
mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
- if (update.addedBubble != null && mStackView != null) {
+ if (update.addedBubble != null) {
mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
- mStackView.addBubble(update.addedBubble);
+ mBubbleViewCallback.addBubble(update.addedBubble);
}
- if (update.updatedBubble != null && mStackView != null) {
- mStackView.updateBubble(update.updatedBubble);
+ if (update.updatedBubble != null) {
+ mBubbleViewCallback.updateBubble(update.updatedBubble);
}
- if (update.suppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.suppressedBubble, true);
+ if (update.suppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true);
}
- if (update.unsuppressedBubble != null && mStackView != null) {
- mStackView.setBubbleSuppressed(update.unsuppressedBubble, false);
+ if (update.unsuppressedBubble != null) {
+ mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false);
}
boolean collapseStack = update.expandedChanged && !update.expanded;
// At this point, the correct bubbles are inflated in the stack.
// Make sure the order in bubble data is reflected in bubble row.
- if (update.orderChanged && mStackView != null) {
+ if (update.orderChanged) {
mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
// if the stack is going to be collapsed, do not update pointer position
// after reordering
- mStackView.updateBubbleOrder(update.bubbles, !collapseStack);
+ mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack);
}
if (collapseStack) {
- mStackView.setExpanded(false);
+ mBubbleViewCallback.expansionChanged(/* expanded= */ false);
mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
}
- if (update.selectionChanged && mStackView != null) {
- mStackView.setSelectedBubble(update.selectedBubble);
+ if (update.selectionChanged) {
+ mBubbleViewCallback.selectionChanged(update.selectedBubble);
}
// Expanding? Apply this last.
if (update.expandedChanged && update.expanded) {
- if (mStackView != null) {
- mStackView.setExpanded(true);
- mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
- }
+ mBubbleViewCallback.expansionChanged(/* expanded= */ true);
+ mSysuiProxy.requestNotificationShadeTopUi(true, TAG);
}
mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate");
@@ -1407,6 +1479,19 @@ public class BubbleController implements ConfigurationChangeListener {
}
};
+ private void updateOverflowButtonDot() {
+ BubbleOverflow overflow = mBubbleData.getOverflow();
+ if (overflow == null) return;
+
+ for (Bubble b : mBubbleData.getOverflowBubbles()) {
+ if (b.showDot()) {
+ overflow.setShowDot(true);
+ return;
+ }
+ }
+ overflow.setShowDot(false);
+ }
+
private boolean handleDismissalInterception(BubbleEntry entry,
@Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
if (isSummaryOfBubbles(entry)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 15f8eaca0833..5ecbd6b596b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1360,16 +1360,6 @@ public class BubbleStackView extends FrameLayout
updateOverflowVisibility();
}
- void updateOverflowButtonDot() {
- for (Bubble b : mBubbleData.getOverflowBubbles()) {
- if (b.showDot()) {
- mBubbleOverflow.setShowDot(true);
- return;
- }
- }
- mBubbleOverflow.setShowDot(false);
- }
-
/**
* Handle theme changes.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 22587f4c6456..8b4ac1a8dc79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -39,6 +39,9 @@ import java.util.List;
*
* Note that most of the implementation here inherits from
* {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ *
+ * Use the {@link TabletopModeController} if you are interested in tabletop mode change only,
+ * which is more common.
*/
public class DevicePostureController {
@IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
new file mode 100644
index 000000000000..bf226283ae54
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.ArraySet;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Wrapper class to track the tabletop (aka. flex) mode change on Fold-ables.
+ * See also <a
+ * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables
+ * #foldable_postures">Foldable states and postures</a> for reference.
+ *
+ * Use the {@link DevicePostureController} for more detailed posture changes.
+ */
+public class TabletopModeController implements
+ DevicePostureController.OnDevicePostureChangedListener,
+ DisplayController.OnDisplaysChangedListener {
+ private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+
+ private final Context mContext;
+
+ private final DevicePostureController mDevicePostureController;
+
+ private final DisplayController mDisplayController;
+
+ private final ShellExecutor mMainExecutor;
+
+ private final Set<Integer> mTabletopModeRotations = new ArraySet<>();
+
+ private final List<OnTabletopModeChangedListener> mListeners = new ArrayList<>();
+
+ @VisibleForTesting
+ final Runnable mOnEnterTabletopModeCallback = () -> {
+ if (isInTabletopMode()) {
+ // We are still in tabletop mode, go ahead.
+ mayBroadcastOnTabletopModeChange(true /* isInTabletopMode */);
+ }
+ };
+
+ @DevicePostureController.DevicePostureInt
+ private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+ @Surface.Rotation
+ private int mDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+ /**
+ * Track the last callback value for {@link OnTabletopModeChangedListener}.
+ * This is to avoid duplicated {@code false} callback to {@link #mListeners}.
+ */
+ private Boolean mLastIsInTabletopModeForCallback;
+
+ public TabletopModeController(Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ mContext = context;
+ mDevicePostureController = postureController;
+ mDisplayController = displayController;
+ mMainExecutor = mainExecutor;
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ @VisibleForTesting
+ void onInit() {
+ mDevicePostureController.registerOnDevicePostureChangedListener(this);
+ mDisplayController.addDisplayWindowListener(this);
+ // Aligns with what's in {@link com.android.server.wm.DisplayRotation}.
+ final int[] deviceTabletopRotations = mContext.getResources().getIntArray(
+ com.android.internal.R.array.config_deviceTabletopRotations);
+ if (deviceTabletopRotations == null || deviceTabletopRotations.length == 0) {
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "No valid config_deviceTabletopRotations, can not tell"
+ + " tabletop mode in WMShell");
+ return;
+ }
+ for (int angle : deviceTabletopRotations) {
+ switch (angle) {
+ case 0:
+ mTabletopModeRotations.add(Surface.ROTATION_0);
+ break;
+ case 90:
+ mTabletopModeRotations.add(Surface.ROTATION_90);
+ break;
+ case 180:
+ mTabletopModeRotations.add(Surface.ROTATION_180);
+ break;
+ case 270:
+ mTabletopModeRotations.add(Surface.ROTATION_270);
+ break;
+ default:
+ ProtoLog.e(WM_SHELL_FOLDABLE,
+ "Invalid surface rotation angle in "
+ + "config_deviceTabletopRotations: %d",
+ angle);
+ break;
+ }
+ }
+ }
+
+ /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
+ public void registerOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ if (listener == null || mListeners.contains(listener)) return;
+ mListeners.add(listener);
+ listener.onTabletopModeChanged(isInTabletopMode());
+ }
+
+ /** Unregister {@link OnTabletopModeChangedListener} for tabletop mode change. */
+ public void unregisterOnTabletopModeChangedListener(
+ @NonNull OnTabletopModeChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ @Override
+ public void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+ if (mDevicePosture != posture) {
+ onDevicePostureOrDisplayRotationChanged(posture, mDisplayRotation);
+ }
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ final int newDisplayRotation = newConfig.windowConfiguration.getDisplayRotation();
+ if (displayId == DEFAULT_DISPLAY && newDisplayRotation != mDisplayRotation) {
+ onDevicePostureOrDisplayRotationChanged(mDevicePosture, newDisplayRotation);
+ }
+ }
+
+ private void onDevicePostureOrDisplayRotationChanged(
+ @DevicePostureController.DevicePostureInt int newPosture,
+ @Surface.Rotation int newDisplayRotation) {
+ final boolean wasInTabletopMode = isInTabletopMode();
+ mDevicePosture = newPosture;
+ mDisplayRotation = newDisplayRotation;
+ final boolean couldBeInTabletopMode = isInTabletopMode();
+ mMainExecutor.removeCallbacks(mOnEnterTabletopModeCallback);
+ if (!wasInTabletopMode && couldBeInTabletopMode) {
+ // May enter tabletop mode, but we need to wait for additional time since this
+ // could be an intermediate state.
+ mMainExecutor.executeDelayed(mOnEnterTabletopModeCallback, TABLETOP_MODE_DELAY_MILLIS);
+ } else {
+ // Cancel entering tabletop mode if any condition's changed.
+ mayBroadcastOnTabletopModeChange(false /* isInTabletopMode */);
+ }
+ }
+
+ private boolean isHalfOpened(@DevicePostureController.DevicePostureInt int posture) {
+ return posture == DEVICE_POSTURE_HALF_OPENED;
+ }
+
+ private boolean isInTabletopMode() {
+ return isHalfOpened(mDevicePosture) && mTabletopModeRotations.contains(mDisplayRotation);
+ }
+
+ private void mayBroadcastOnTabletopModeChange(boolean isInTabletopMode) {
+ if (mLastIsInTabletopModeForCallback == null
+ || mLastIsInTabletopModeForCallback != isInTabletopMode) {
+ mListeners.forEach(l -> l.onTabletopModeChanged(isInTabletopMode));
+ mLastIsInTabletopModeForCallback = isInTabletopMode;
+ }
+ }
+
+ /**
+ * Listener interface for tabletop mode change.
+ */
+ public interface OnTabletopModeChangedListener {
+ /**
+ * Callback when tabletop mode changes. Expect duplicated callbacks with {@code false}.
+ * @param isInTabletopMode {@code true} if enters tabletop mode, {@code false} otherwise.
+ */
+ void onTabletopModeChanged(boolean isInTabletopMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index abb357c5b653..bdf0ac2ed30c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -247,11 +247,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
- if (mScreenshot != null) {
- if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
- mScreenshotAnimator.cancel();
- }
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+ if (mScreenshot != null) {
t.setPosition(mScreenshot, mOffsetX, mOffsetY);
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
@@ -321,6 +321,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Screenshot host leash and attach on it if meet some conditions */
public void screenshotIfNeeded(SurfaceControl.Transaction t) {
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mTempRect.set(mOldBounds);
mTempRect.offsetTo(0, 0);
mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
@@ -333,6 +337,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
if (screenshot == null || !screenshot.isValid()) return;
if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+ if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+ mScreenshotAnimator.cancel();
+ }
+
mScreenshot = screenshot;
t.reparent(screenshot, mHostLeash);
t.setLayer(screenshot, Integer.MAX_VALUE - 1);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 72dc771ee08c..ef21c7e9ec0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -51,6 +51,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
@@ -171,6 +172,18 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static TabletopModeController provideTabletopModeController(
+ Context context,
+ ShellInit shellInit,
+ DevicePostureController postureController,
+ DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new TabletopModeController(
+ context, shellInit, postureController, displayController, mainExecutor);
+ }
+
+ @WMSingleton
+ @Provides
static DragAndDropController provideDragAndDropController(Context context,
ShellInit shellInit,
ShellController shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index ac13f96585b6..f9e0ca53b32d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -247,6 +247,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
mLaunchRootTask = taskInfo;
}
+ if (mHomeTask != null && mHomeTask.taskId == taskInfo.taskId
+ && !taskInfo.equals(mHomeTask)) {
+ mHomeTask = taskInfo;
+ }
+
super.onTaskInfoChanged(taskInfo);
}
@@ -364,6 +369,7 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
final WindowContainerTransaction wct = getWindowContainerTransaction();
final Rect taskBounds = calculateBounds();
wct.setBounds(mLaunchRootTask.token, taskBounds);
+ wct.setBounds(mHomeTask.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
mSyncQueue.queue(wct);
final SurfaceControl finalLeash = mLaunchRootLeash;
mSyncQueue.runInSync(t -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 2624ee536b58..d961d8658b98 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -70,4 +70,9 @@ interface IPip {
* Sets the next pip animation type to be the alpha animation.
*/
oneway void setPipAnimationTypeToAlpha() = 5;
+
+ /**
+ * Sets the height and visibility of the Launcher keep clear area.
+ */
+ oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e9d257139779..eb336d56b62c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1179,6 +1179,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
+ * Directly update the animator bounds.
+ */
+ public void updateAnimatorBounds(Rect bounds) {
+ final PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
+ animator.updateEndValue(bounds);
+ }
+ animator.setDestinationBounds(bounds);
+ }
+ }
+
+ /**
* Handles all changes to the PictureInPictureParams.
*/
protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
index c6b5ce93fd35..db6138a0891f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -93,6 +93,11 @@ public class PipTransitionState {
return hasEnteredPip(mState);
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public boolean isEnteringPip() {
+ return isEnteringPip(mState);
+ }
+
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
mInSwipePipToHomeTransition = inSwipePipToHomeTransition;
}
@@ -130,6 +135,11 @@ public class PipTransitionState {
return state == ENTERED_PIP;
}
+ /** Returns true if activity is currently entering PiP mode. */
+ public static boolean isEnteringPip(@TransitionState int state) {
+ return state == ENTERING_PIP;
+ }
+
public interface OnPipTransitionStateChangedListener {
void onPipTransitionStateChanged(@TransitionState int oldState,
@TransitionState int newState);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index fa3efeb51bd0..0d5f1432d204 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -101,6 +101,7 @@ import com.android.wm.shell.sysui.UserChangeListener;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -181,14 +182,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb
// early bail out if the keep clear areas feature is disabled
return;
}
- // only move if already in pip, other transitions account for keep clear areas
- if (mPipTransitionState.hasEnteredPip()) {
+ // only move if we're in PiP or transitioning into PiP
+ if (!mPipTransitionState.shouldBlockResizeRequest()) {
Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState,
mPipBoundsAlgorithm);
// only move if the bounds are actually different
if (destBounds != mPipBoundsState.getBounds()) {
- mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
- mEnterAnimationDuration, null);
+ if (mPipTransitionState.hasEnteredPip()) {
+ // if already in PiP, schedule separate animation
+ mPipTaskOrganizer.scheduleAnimateResizePip(destBounds,
+ mEnterAnimationDuration, null);
+ } else if (mPipTransitionState.isEnteringPip()) {
+ // while entering PiP we just need to update animator bounds
+ mPipTaskOrganizer.updateAnimatorBounds(destBounds);
+ }
}
}
}
@@ -874,6 +881,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
+ private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ if (visible) {
+ Rect rect = new Rect(
+ 0, mPipBoundsState.getDisplayBounds().bottom - height,
+ mPipBoundsState.getDisplayBounds().right,
+ mPipBoundsState.getDisplayBounds().bottom);
+ Set<Rect> restrictedKeepClearAreas = new HashSet<>(
+ mPipBoundsState.getRestrictedKeepClearAreas());
+ restrictedKeepClearAreas.add(rect);
+ mPipBoundsState.setKeepClearAreas(restrictedKeepClearAreas,
+ mPipBoundsState.getUnrestrictedKeepClearAreas());
+ updatePipPositionForKeepClearAreas();
+ }
+ }
+
private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
mOnIsInPipStateChangedListener = callback;
if (mOnIsInPipStateChangedListener != null) {
@@ -1237,6 +1259,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
+ public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
+ executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight",
+ (controller) -> {
+ controller.setLauncherKeepClearAreaHeight(visible, height);
+ });
+ }
+
+ @Override
public void setPipAnimationListener(IPipAnimationListener listener) {
executeRemoteCallWithTaskPermission(mController, "setPipAnimationListener",
(controller) -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 75f9a4c33af9..c9b3a1af6507 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -50,6 +50,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c0e48105ffbe..71ee690146f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -207,6 +207,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private boolean mIsDividerRemoteAnimating;
private boolean mIsDropEntering;
private boolean mIsExiting;
+ private boolean mIsRootTranslucent;
private DefaultMixedHandler mMixedHandler;
private final Toast mSplitUnsupportedToast;
@@ -422,6 +423,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ if (!isSplitActive()) {
+ // prevent the fling divider to center transitioni if split screen didn't active.
+ mIsDropEntering = true;
+ }
+
setSideStagePosition(sideStagePosition, wct);
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
targetStage.evictAllChildren(evictWct);
@@ -436,28 +442,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// reparent the task to an invisible split root will make the activity invisible. Reorder
// the root task to front to make the entering transition from pip to split smooth.
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
wct.reorder(targetStage.mRootTaskInfo.token, true);
- wct.setForceTranslucent(targetStage.mRootTaskInfo.token, true);
- // prevent the fling divider to center transition
- mIsDropEntering = true;
-
targetStage.addTask(task, wct);
- if (ENABLE_SHELL_TRANSITIONS) {
- prepareEnterSplitScreen(wct);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
- null, this, null /* consumedCallback */, (finishWct, finishT) -> {
- if (!evictWct.isEmpty()) {
- finishWct.merge(evictWct, true);
- }
- } /* finishedCallback */);
- } else {
- if (!evictWct.isEmpty()) {
- wct.merge(evictWct, true /* transfer */);
- }
- mTaskOrganizer.applyTransaction(wct);
+ if (!evictWct.isEmpty()) {
+ wct.merge(evictWct, true /* transfer */);
}
+ mTaskOrganizer.applyTransaction(wct);
return true;
}
@@ -716,7 +707,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.setDivideRatio(splitRatio);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
// Make sure the launch options will put tasks in the corresponding split roots
mainOptions = mainOptions != null ? mainOptions : new Bundle();
@@ -923,7 +914,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
// TODO(b/268008375): Merge APIs to start a split pair into one.
if (mainTaskId != INVALID_TASK_ID) {
@@ -1344,7 +1335,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.removeAllTasks(wct, false /* toTop */);
mMainStage.deactivate(wct, false /* toTop */);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
onTransitionAnimationComplete();
} else {
@@ -1376,7 +1367,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.deactivate(finishedWCT, childrenToTop == mMainStage /* toTop */);
mSideStage.removeAllTasks(finishedWCT, childrenToTop == mSideStage /* toTop */);
finishedWCT.reorder(mRootTaskInfo.token, false /* toTop */);
- finishedWCT.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
finishedWCT.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(finishedWCT);
mSyncQueue.runInSync(at -> {
@@ -1488,7 +1479,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
@@ -1692,6 +1683,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskInfo = null;
mRootTaskLeash = null;
+ mIsRootTranslucent = false;
}
@@ -1710,7 +1702,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
mSyncQueue.queue(wct);
@@ -1734,7 +1726,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictOtherChildren(wct, taskId);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
@@ -1758,6 +1750,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
}
+ private void setRootForceTranslucent(boolean translucent, WindowContainerTransaction wct) {
+ if (mIsRootTranslucent == translucent) return;
+
+ mIsRootTranslucent = translucent;
+ wct.setForceTranslucent(mRootTaskInfo.token, translucent);
+ }
+
private void onStageVisibilityChanged(StageListenerImpl stageListener) {
// If split didn't active, just ignore this callback because we should already did these
// on #applyExitSplitScreen.
@@ -1784,10 +1783,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Split entering background.
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
true /* setReparentLeafTaskIfRelaunch */);
- wct.setForceTranslucent(mRootTaskInfo.token, true);
+ setRootForceTranslucent(true, wct);
} else {
wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
false /* setReparentLeafTaskIfRelaunch */);
+ setRootForceTranslucent(false, wct);
}
mSyncQueue.queue(wct);
@@ -1919,7 +1919,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStage.activate(wct, true /* includingTopTask */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
+ setRootForceTranslucent(false, wct);
}
mSyncQueue.queue(wct);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9224b3cbd798..6b7ca421f5ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -193,6 +193,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final DragDetector mDragDetector;
private int mDragPointerId = -1;
+ private boolean mIsDragging;
private CaptionTouchEventListener(
RunningTaskInfo taskInfo,
@@ -223,19 +224,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (v.getId() != R.id.caption) {
return false;
}
- mDragDetector.onMotionEvent(e);
-
- if (e.getAction() != MotionEvent.ACTION_DOWN) {
- return false;
- }
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return false;
+ if (e.getAction() == MotionEvent.ACTION_DOWN) {
+ final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (!taskInfo.isFocused) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, true /* onTop */);
+ mSyncQueue.queue(wct);
+ }
}
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.reorder(mTaskToken, true /* onTop */);
- mSyncQueue.queue(wct);
- return true;
+ return mDragDetector.onMotionEvent(e);
}
/**
@@ -253,20 +250,24 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
+ mIsDragging = false;
+ return false;
}
case MotionEvent.ACTION_MOVE: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ mIsDragging = true;
+ return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
}
}
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index c517f7be330b..dee5f8fa74d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -223,6 +223,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DragPositioningCallback mDragPositioningCallback;
private final DragDetector mDragDetector;
+ private boolean mIsDragging;
private int mDragPointerId = -1;
private DesktopModeTouchEventListener(
@@ -273,23 +274,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
return false;
}
- switch (e.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mDragDetector.onMotionEvent(e);
- final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- if (taskInfo.isFocused) {
- return mDragDetector.isDragEvent();
- }
- return false;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- boolean res = mDragDetector.isDragEvent();
- mDragDetector.onMotionEvent(e);
- return res;
- default:
- mDragDetector.onMotionEvent(e);
- return mDragDetector.isDragEvent();
- }
+ return mDragDetector.onMotionEvent(e);
}
/**
@@ -313,13 +298,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragPointerId = e.getPointerId(0);
mDragPositioningCallback.onDragPositioningStart(
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
- break;
+ mIsDragging = false;
+ return false;
}
case MotionEvent.ACTION_MOVE: {
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- break;
+ mIsDragging = true;
+ return true;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
@@ -336,7 +323,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
c -> c.moveToFullscreen(taskInfo));
}
}
- break;
+ final boolean wasDragging = mIsDragging;
+ mIsDragging = false;
+ return wasDragging;
}
}
return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index cf1850b92373..65b5a7a17afe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -56,10 +56,15 @@ class DragDetector {
* {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
*/
boolean onMotionEvent(MotionEvent ev) {
+ final boolean isTouchScreen =
+ (ev.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
+ if (!isTouchScreen) {
+ // Only touches generate noisy moves, so mouse/trackpad events don't need to filtered
+ // to take the slop threshold into consideration.
+ return mEventHandler.handleMotionEvent(ev);
+ }
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
- // Only touch screens generate noisy moves.
- mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
@@ -72,8 +77,12 @@ class DragDetector {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
+ // Touches generate noisy moves, so only once the move is past the touch
+ // slop threshold should it be considered a drag.
mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
}
+ // The event handler should only be notified about 'move' events if a drag has been
+ // detected.
if (mIsDragEvent) {
return mEventHandler.handleMotionEvent(ev);
} else {
@@ -94,10 +103,6 @@ class DragDetector {
mTouchSlop = touchSlop;
}
- boolean isDragEvent() {
- return mIsDragEvent;
- }
-
private void resetState() {
mIsDragEvent = false;
mInputDownPoint.set(0, 0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index a3d364a0068e..0bce3acecb3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,7 @@ class TaskPositioner implements DragPositioningCallback {
private final DisplayController mDisplayController;
private final WindowDecoration mWindowDecoration;
+ private final Rect mTempBounds = new Rect();
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mRepositionStartPoint = new PointF();
private final Rect mRepositionTaskBounds = new Rect();
@@ -117,17 +118,32 @@ class TaskPositioner implements DragPositioningCallback {
final float deltaX = x - mRepositionStartPoint.x;
final float deltaY = y - mRepositionStartPoint.y;
mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
+
+ final Rect stableBounds = mTempBounds;
+ // Make sure the new resizing destination in any direction falls within the stable bounds.
+ // If not, set the bounds back to the old location that was valid to avoid conflicts with
+ // some regions such as the gesture area.
+ mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
+ .getStableBounds(stableBounds);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
- mRepositionTaskBounds.left += deltaX;
+ final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX;
+ mRepositionTaskBounds.left = (candidateLeft > stableBounds.left)
+ ? candidateLeft : oldLeft;
}
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
- mRepositionTaskBounds.right += deltaX;
+ final int candidateRight = mRepositionTaskBounds.right + (int) deltaX;
+ mRepositionTaskBounds.right = (candidateRight < stableBounds.right)
+ ? candidateRight : oldRight;
}
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
- mRepositionTaskBounds.top += deltaY;
+ final int candidateTop = mRepositionTaskBounds.top + (int) deltaY;
+ mRepositionTaskBounds.top = (candidateTop > stableBounds.top)
+ ? candidateTop : oldTop;
}
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
- mRepositionTaskBounds.bottom += deltaY;
+ final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY;
+ mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
+ ? candidateBottom : oldBottom;
}
if (mCtrlType == CTRL_TYPE_UNDEFINED) {
mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
new file mode 100644
index 000000000000..96d202ce3a85
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_OPENED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link TabletopModeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class TabletopModeControllerTest extends ShellTestCase {
+ // It's considered tabletop mode if the display rotation angle matches what's in this array.
+ // It's defined as com.android.internal.R.array.config_deviceTabletopRotations on real devices.
+ private static final int[] TABLETOP_MODE_ROTATIONS = new int[] {
+ 90 /* Surface.ROTATION_90 */,
+ 270 /* Surface.ROTATION_270 */
+ };
+
+ private TestShellExecutor mMainExecutor;
+
+ private Configuration mConfiguration;
+
+ private TabletopModeController mPipTabletopController;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private ShellInit mShellInit;
+
+ @Mock
+ private Resources mResources;
+
+ @Mock
+ private DevicePostureController mDevicePostureController;
+
+ @Mock
+ private DisplayController mDisplayController;
+
+ @Mock
+ private TabletopModeController.OnTabletopModeChangedListener mOnTabletopModeChangedListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mResources.getIntArray(com.android.internal.R.array.config_deviceTabletopRotations))
+ .thenReturn(TABLETOP_MODE_ROTATIONS);
+ when(mContext.getResources()).thenReturn(mResources);
+ mMainExecutor = new TestShellExecutor();
+ mConfiguration = new Configuration();
+ mPipTabletopController = new TabletopModeController(mContext, mShellInit,
+ mDevicePostureController, mDisplayController, mMainExecutor);
+ mPipTabletopController.onInit();
+ }
+
+ @Test
+ public void instantiateController_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipTabletopController));
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopMode_callbackFalse() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(false);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_inTabletopMode_callbackTrue() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+
+ verify(mOnTabletopModeChangedListener, times(1))
+ .onTabletopModeChanged(true);
+ }
+
+ @Test
+ public void registerOnTabletopModeChangedListener_notInTabletopModeTwice_callbackOnce() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.registerOnTabletopModeChangedListener(
+ mOnTabletopModeChangedListener);
+ clearInvocations(mOnTabletopModeChangedListener);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ verifyZeroInteractions(mOnTabletopModeChangedListener);
+ }
+
+ // Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
+ @Test
+ public void foldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void foldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ // Test cases starting from unfolded state (DEVICE_POSTURE_OPENED)
+ @Test
+ public void unfoldedRotation90_halfOpen_scheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+
+ @Test
+ public void unfoldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+ mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+ mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+ assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
index 8f66f4e7e47b..94c064bda763 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -5,13 +5,16 @@ import android.app.WindowConfiguration
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
+import android.view.Display
import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
import androidx.test.filters.SmallTest
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
@@ -19,10 +22,11 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
/**
@@ -51,6 +55,8 @@ class TaskPositionerTest : ShellTestCase() {
private lateinit var mockDisplayController: DisplayController
@Mock
private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock
+ private lateinit var mockDisplay: Display
private lateinit var taskPositioner: TaskPositioner
@@ -68,6 +74,9 @@ class TaskPositionerTest : ShellTestCase() {
`when`(taskToken.asBinder()).thenReturn(taskBinder)
`when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
`when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
taskId = TASK_ID
@@ -78,6 +87,8 @@ class TaskPositionerTest : ShellTestCase() {
displayId = DISPLAY_ID
configuration.windowConfiguration.bounds = STARTING_BOUNDS
}
+ mockWindowDecoration.mDisplay = mockDisplay
+ `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
}
@Test
@@ -451,6 +462,72 @@ class TaskPositionerTest : ShellTestCase() {
})
}
+ fun testDragResize_toDisallowedBounds_freezesAtLimit() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat()
+ )
+
+ // Resize the task by 10px to the right and bottom, a valid destination
+ val newBounds = Rect(
+ STARTING_BOUNDS.left,
+ STARTING_BOUNDS.top,
+ STARTING_BOUNDS.right + 10,
+ STARTING_BOUNDS.bottom + 10)
+ taskPositioner.onDragPositioningMove(
+ newBounds.right.toFloat(),
+ newBounds.bottom.toFloat()
+ )
+
+ // Resize the task by another 10px to the right (allowed) and to just in the disallowed
+ // area of the Y coordinate.
+ val newBounds2 = Rect(
+ newBounds.left,
+ newBounds.top,
+ newBounds.right + 10,
+ DISALLOWED_RESIZE_AREA.top
+ )
+ taskPositioner.onDragPositioningMove(
+ newBounds2.right.toFloat(),
+ newBounds2.bottom.toFloat()
+ )
+
+ taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat())
+
+ // The first resize falls in the allowed area, verify there's a change for it.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds)
+ }
+ })
+ // The second resize falls in the disallowed area, verify there's no change for it.
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(newBounds2)
+ }
+ })
+ // Instead, there should be a change for its allowed portion (the X movement) with the Y
+ // staying frozen in the last valid resize position.
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder && change.ofBounds(
+ Rect(
+ newBounds2.left,
+ newBounds2.top,
+ newBounds2.right,
+ newBounds.bottom // Stayed at the first resize destination.
+ )
+ )
+ }
+ })
+ }
+
+ private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
+ return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) &&
+ bounds == configuration.windowConfiguration.bounds
+ }
+
companion object {
private const val TASK_ID = 5
private const val MIN_WIDTH = 10
@@ -458,6 +535,19 @@ class TaskPositionerTest : ShellTestCase() {
private const val DENSITY_DPI = 20
private const val DEFAULT_MIN = 40
private const val DISPLAY_ID = 1
+ private const val NAVBAR_HEIGHT = 50
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ private val DISALLOWED_RESIZE_AREA = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom)
+ private val STABLE_BOUNDS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
}
}
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index 9ca391013aa3..2273f816ac60 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -1,2 +1,4 @@
michaelwr@google.com
santoscordon@google.com
+chaviw@google.com
+nmusgrave@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 290328894439..f0a82113c3a3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -19,14 +19,15 @@ package com.android.systemui.animation
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.util.MathUtils
+import android.util.MathUtils.abs
+import java.lang.Float.max
+import java.lang.Float.min
private const val TAG_WGHT = "wght"
private const val TAG_ITAL = "ital"
-private const val FONT_WEIGHT_MAX = 1000f
-private const val FONT_WEIGHT_MIN = 0f
-private const val FONT_WEIGHT_ANIMATION_STEP = 10f
private const val FONT_WEIGHT_DEFAULT_VALUE = 400f
+private const val FONT_WEIGHT_ANIMATION_FRAME_COUNT = 100
private const val FONT_ITALIC_MAX = 1f
private const val FONT_ITALIC_MIN = 0f
@@ -118,14 +119,17 @@ class FontInterpolator {
lerp(startAxes, endAxes) { tag, startValue, endValue ->
when (tag) {
// TODO: Good to parse 'fvar' table for retrieving default value.
- TAG_WGHT ->
- adjustWeight(
+ TAG_WGHT -> {
+ adaptiveAdjustWeight(
MathUtils.lerp(
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
progress
- )
+ ),
+ startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
+ endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
)
+ }
TAG_ITAL ->
adjustItalic(
MathUtils.lerp(
@@ -205,10 +209,14 @@ class FontInterpolator {
return result
}
- // For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
+ // For the performance reasons, we animate weight with adaptive step. This helps
// Cache hit ratio in the Skia glyph cache.
- private fun adjustWeight(value: Float) =
- coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
+ // The reason we don't use fix step is because the range of weight axis is not normalized,
+ // some are from 50 to 100, others are from 0 to 1000, so we cannot give a constant proper step
+ private fun adaptiveAdjustWeight(value: Float, start: Float, end: Float): Float {
+ val step = max(abs(end - start) / FONT_WEIGHT_ANIMATION_FRAME_COUNT, 1F)
+ return coerceInWithStep(value, min(start, end), max(start, end), step)
+ }
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
// Cache hit ratio in the Skia glyph cache.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index a08b59829de2..7fe94d349c42 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -190,6 +190,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
* @param textSize an optional font size.
* @param colors an optional colors array that must be the same size as numLines passed to
* the TextInterpolator
+ * @param strokeWidth an optional paint stroke width
* @param animate an optional boolean indicating true for showing style transition as animation,
* false for immediate style transition. True by default.
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -201,6 +202,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
weight: Int = -1,
textSize: Float = -1f,
color: Int? = null,
+ strokeWidth: Float = -1f,
animate: Boolean = true,
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
@@ -254,6 +256,9 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
if (color != null) {
textInterpolator.targetPaint.color = color
}
+ if (strokeWidth >= 0F) {
+ textInterpolator.targetPaint.strokeWidth = strokeWidth
+ }
textInterpolator.onTargetPaintModified()
if (animate) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 468a8b10bc01..3eb7fd84dc49 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -473,6 +473,7 @@ class TextInterpolator(layout: Layout) {
// TODO(172943390): Add other interpolation or support custom interpolator.
out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress)
out.color = ColorUtils.blendARGB(from.color, to.color, progress)
+ out.strokeWidth = MathUtils.lerp(from.strokeWidth, to.strokeWidth, progress)
}
// Shape the text and stores the result to out argument.
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
index f64ea4561906..459a38e9318c 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -25,6 +25,7 @@ import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.regex.Pattern
import org.jetbrains.uast.UAnnotation
import org.jetbrains.uast.UElement
@@ -36,22 +37,38 @@ class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner {
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun visitAnnotation(node: UAnnotation) {
- if (node.qualifiedName !in DEMOTING_ANNOTATION) {
- return
+ // Annotations having int bugId field
+ if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) {
+ val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
+ if (bugId <= 0) {
+ val location = context.getLocation(node)
+ val message = "Please attach a bug id to track demoted test"
+ context.report(ISSUE, node, location, message)
+ }
}
- val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
- if (bugId <= 0) {
- val location = context.getLocation(node)
- val message = "Please attach a bug id to track demoted test"
- context.report(ISSUE, node, location, message)
+ // @Ignore has a String field for reason
+ if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) {
+ val reason = node.findAttributeValue("value")!!.evaluate() as String
+ val bugPattern = Pattern.compile("b/\\d+")
+ if (!bugPattern.matcher(reason).find()) {
+ val location = context.getLocation(node)
+ val message = "Please attach a bug (e.g. b/123) to track demoted test"
+ context.report(ISSUE, node, location, message)
+ }
}
}
}
}
companion object {
- val DEMOTING_ANNOTATION =
- listOf("androidx.test.filters.FlakyTest", "android.platform.test.annotations.FlakyTest")
+ val DEMOTING_ANNOTATION_BUG_ID =
+ listOf(
+ "androidx.test.filters.FlakyTest",
+ "android.platform.test.annotations.FlakyTest",
+ "android.platform.test.rule.PlatinumRule.Platinum",
+ )
+
+ const val DEMOTING_ANNOTATION_IGNORE = "org.junit.Ignore"
@JvmField
val ISSUE: Issue =
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index 557c300635eb..63eb2632979c 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -127,6 +127,139 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
)
}
+ @Test
+ fun testExcludeDevices_withBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.rule.PlatinumRule.Platinum;
+
+ @Platinum(devices = "foo,bar", bugId = 123)
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testExcludeDevices_withoutBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.rule.PlatinumRule.Platinum;
+
+ @Platinum(devices = "foo,bar")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug id to track demoted test [DemotingTestWithoutBug]
+ @Platinum(devices = "foo,bar")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ @Test
+ fun testIgnore_withBug() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore("Blocked by b/123.")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testIgnore_withoutBug() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+ @Ignore
+ ~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import org.junit.Ignore;
+
+ @Ignore("Not ready")
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+ @Ignore("Not ready")
+ ~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
private val filtersFlakyTestStub: TestFile =
java(
"""
@@ -147,5 +280,34 @@ class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
}
"""
)
- private val stubs = arrayOf(filtersFlakyTestStub, annotationsFlakyTestStub)
+ private val annotationsPlatinumStub: TestFile =
+ java(
+ """
+ package android.platform.test.rule;
+
+ public class PlatinumRule {
+ public @interface Platinum {
+ String devices();
+ int bugId() default -1;
+ }
+ }
+ """
+ )
+ private val annotationsIgnoreStub: TestFile =
+ java(
+ """
+ package org.junit;
+
+ public @interface Ignore {
+ String value() default "";
+ }
+ """
+ )
+ private val stubs =
+ arrayOf(
+ filtersFlakyTestStub,
+ annotationsFlakyTestStub,
+ annotationsPlatinumStub,
+ annotationsIgnoreStub
+ )
}
diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index f1aa54412b3b..a4e7a5f12db4 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -27,6 +27,4 @@
<integer name="scaled_password_text_size">26</integer>
<dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 992d143ff66d..edd6eff92c1c 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -123,9 +123,7 @@
<dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
<dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
<dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
- <dimen name="bouncer_user_switcher_y_trans">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_y_trans">80dp</dimen>
<!-- 2 * the margin + size should equal the plus_margin -->
<dimen name="user_switcher_icon_large_margin">16dp</dimen>
diff --git a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
new file mode 100644
index 000000000000..d123caf82ed5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
@@ -0,0 +1,12 @@
+<vector android:height="11dp" android:viewportHeight="12"
+ android:viewportWidth="22" android:width="20.166666dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <group>
+ <clip-path android:pathData="M0,0.5h22v11h-22z"/>
+ <path android:fillColor="#231F20" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
+ <path android:fillColor="#231F20" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
+ <path android:fillColor="#231F20" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
+ <path android:fillColor="#231F20" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
+ <path android:fillColor="#231F20" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
+ <path android:fillColor="#231F20" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 4f38e6058723..908aac4a7b7f 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -66,4 +66,8 @@
<dimen name="controls_header_horizontal_padding">12dp</dimen>
<dimen name="controls_content_margin_horizontal">16dp</dimen>
+
+ <!-- Bouncer user switcher margins -->
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
index b98165fb08f0..ca62d286f4ee 100644
--- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -21,6 +21,6 @@
<!-- Space between status view and notification shelf -->
<dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
<dimen name="keyguard_clock_top_margin">80dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen>
- <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">155dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">85dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ca4217f64b60..ac4c4929bab2 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -207,6 +207,11 @@
<color name="control_thumbnail_shadow_color">@*android:color/black</color>
<color name="controls_task_view_bg">#CC191C1D</color>
+ <!-- Keyboard backlight indicator-->
+ <color name="backlight_indicator_step_filled">#F6E388</color>
+ <color name="backlight_indicator_step_empty">#494740</color>
+ <color name="backlight_indicator_background">#32302A</color>
+
<!-- Docked misalignment message -->
<color name="misalignment_text_color">#F28B82</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3b8f1a7d1e4d..35fc9b69e1f4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1653,6 +1653,19 @@
<dimen name="media_output_broadcast_info_summary_height">20dp</dimen>
<dimen name="media_output_broadcast_info_edit">18dp</dimen>
+ <!-- Keyboard backlight indicator-->
+ <dimen name="backlight_indicator_root_corner_radius">48dp</dimen>
+ <dimen name="backlight_indicator_root_vertical_padding">8dp</dimen>
+ <dimen name="backlight_indicator_root_horizontal_padding">4dp</dimen>
+ <dimen name="backlight_indicator_icon_width">22dp</dimen>
+ <dimen name="backlight_indicator_icon_height">11dp</dimen>
+ <dimen name="backlight_indicator_icon_left_margin">2dp</dimen>
+ <dimen name="backlight_indicator_step_width">52dp</dimen>
+ <dimen name="backlight_indicator_step_height">40dp</dimen>
+ <dimen name="backlight_indicator_step_horizontal_margin">4dp</dimen>
+ <dimen name="backlight_indicator_step_small_radius">4dp</dimen>
+ <dimen name="backlight_indicator_step_large_radius">48dp</dimen>
+
<!-- Broadcast dialog -->
<dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen>
<dimen name="broadcast_dialog_title_text_size">24sp</dimen>
@@ -1701,4 +1714,9 @@
it is long-pressed.
-->
<dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
+
+
+ <!-- Bouncer user switcher margins -->
+ <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+ <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8be599867eb4..9a9f5106b7d8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2258,7 +2258,7 @@
<!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
- <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string>
+ <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show here.</string>
<!-- Shows in a dialog presented to the user to authorize this app removal from a Device
controls panel [CHAR LIMIT=NONE] -->
@@ -2318,7 +2318,7 @@
<!-- Title of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
<string name="controls_settings_trivial_controls_dialog_title">Control devices from lock screen?</string>
<!-- Message of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
- <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet.\n\nYour device app determines which devices can be controlled in this way.</string>
+ <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet. Your device app determines which devices can be controlled in this way.</string>
<!-- Neutral button title of the controls dialog [CHAR LIMIT=NONE] -->
<string name="controls_settings_dialog_neutral_button">No thanks</string>
<!-- Positive button title of the controls dialog [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 9a581aaa9b2c..482158e80d0f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -91,6 +91,22 @@ constructor(
val sampledRegion = calculateSampledRegion(sampledView)
val regions = ArrayList<RectF>()
val sampledRegionWithOffset = convertBounds(sampledRegion)
+
+ if (
+ sampledRegionWithOffset.left < 0.0 ||
+ sampledRegionWithOffset.right > 1.0 ||
+ sampledRegionWithOffset.top < 0.0 ||
+ sampledRegionWithOffset.bottom > 1.0
+ ) {
+ android.util.Log.e(
+ "RegionSampler",
+ "view out of bounds: $sampledRegion | " +
+ "screen width: ${displaySize.x}, screen height: ${displaySize.y}",
+ Exception()
+ )
+ return
+ }
+
regions.add(sampledRegionWithOffset)
wallpaperManager?.removeOnColorsChangedListener(this)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 359da13a9799..5b27c40740da 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -29,8 +29,11 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.SuppressLint;
import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -86,6 +89,7 @@ public class RotationButtonController {
private RotationButton mRotationButton;
private boolean mIsRecentsAnimationRunning;
+ private boolean mDocked;
private boolean mHomeRotationEnabled;
private int mLastRotationSuggestion;
private boolean mPendingRotationSuggestion;
@@ -123,6 +127,12 @@ public class RotationButtonController {
() -> mPendingRotationSuggestion = false;
private Animator mRotateHideAnimator;
+ private final BroadcastReceiver mDockedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateDockedState(intent);
+ }
+ };
private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
@Override
@@ -136,7 +146,8 @@ public class RotationButtonController {
// The isVisible check makes the rotation button disappear when we are not locked
// (e.g. for tabletop auto-rotate).
if (rotationLocked || mRotationButton.isVisible()) {
- if (shouldOverrideUserLockPrefs(rotation) && rotationLocked) {
+ // Do not allow a change in rotation to set user rotation when docked.
+ if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
setRotationLockedAtAngle(rotation);
}
setRotateSuggestionButtonState(false /* visible */, true /* forced */);
@@ -214,6 +225,10 @@ public class RotationButtonController {
}
mListenersRegistered = true;
+
+ updateDockedState(mContext.registerReceiver(mDockedReceiver,
+ new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+
try {
WindowManagerGlobal.getWindowManagerService()
.watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
@@ -234,6 +249,8 @@ public class RotationButtonController {
}
mListenersRegistered = false;
+
+ mContext.unregisterReceiver(mDockedReceiver);
try {
WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
} catch (RemoteException e) {
@@ -345,6 +362,15 @@ public class RotationButtonController {
updateRotationButtonStateInOverview();
}
+ private void updateDockedState(Intent intent) {
+ if (intent == null) {
+ return;
+ }
+
+ mDocked = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED)
+ != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+
private void updateRotationButtonStateInOverview() {
if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) {
setRotateSuggestionButtonState(false, true /* hideImmediately */);
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 8323d0971ad7..f005bab55de6 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -23,6 +23,8 @@ import com.android.systemui.util.settings.SettingsUtilModule
import dagger.Binds
import dagger.Module
import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
@Module(includes = [
FeatureFlagsDebugStartableModule::class,
@@ -35,7 +37,8 @@ abstract class FlagsModule {
abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
@Binds
- abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+ @IntoSet
+ abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
@Module
companion object {
@@ -44,5 +47,10 @@ abstract class FlagsModule {
fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
return FlagManager(context, handler)
}
+
+ @JvmStatic
+ @Provides
+ @Named(ConditionalRestarter.RESTART_DELAY)
+ fun provideRestartDelaySec(): Long = 1
}
}
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 87beff76290d..927d4604b823 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,9 @@ package com.android.systemui.flags
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
@Module(includes = [
FeatureFlagsReleaseStartableModule::class,
@@ -29,5 +32,18 @@ abstract class FlagsModule {
abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
@Binds
- abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
+ @IntoSet
+ abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
+
+ @Binds
+ @IntoSet
+ abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
+
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @Named(ConditionalRestarter.RESTART_DELAY)
+ fun provideRestartDelaySec(): Long = 30
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 92ee37310130..4aaa566eb852 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
+import android.graphics.Rect
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
@@ -119,10 +120,6 @@ constructor(
private val mLayoutChangedListener =
object : View.OnLayoutChangeListener {
- private var currentSmallClockView: View? = null
- private var currentLargeClockView: View? = null
- private var currentSmallClockLocation = IntArray(2)
- private var currentLargeClockLocation = IntArray(2)
override fun onLayoutChange(
view: View?,
@@ -135,6 +132,8 @@ constructor(
oldRight: Int,
oldBottom: Int
) {
+ view?.removeOnLayoutChangeListener(this)
+
val parent = (view?.parent) as FrameLayout
// don't pass in negative bounds when clocks are in transition state
@@ -142,31 +141,12 @@ constructor(
return
}
- // SMALL CLOCK
- if (parent.id == R.id.lockscreen_clock_view) {
- // view bounds have changed due to clock size changing (i.e. different character
- // widths)
- // AND/OR the view has been translated when transitioning between small and
- // large clock
- if (
- view != currentSmallClockView ||
- !view.locationOnScreen.contentEquals(currentSmallClockLocation)
- ) {
- currentSmallClockView = view
- currentSmallClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
- }
- // LARGE CLOCK
- else if (parent.id == R.id.lockscreen_clock_view_large) {
- if (
- view != currentLargeClockView ||
- !view.locationOnScreen.contentEquals(currentLargeClockLocation)
- ) {
- currentLargeClockView = view
- currentLargeClockLocation = view.locationOnScreen
- updateRegionSampler(view)
- }
+ val currentViewRect = Rect(left, top, right, bottom)
+ val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
+
+ if (currentViewRect.width() != oldViewRect.width() ||
+ currentViewRect.height() != oldViewRect.height()) {
+ updateRegionSampler(view)
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 67e3400670ba..03947542d21e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -217,9 +217,11 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
private void animate(float progress) {
Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
+ float standardProgress = standardDecelerate.getInterpolation(progress);
mBouncerMessageView.setTranslationY(
- mYTrans - mYTrans * standardDecelerate.getInterpolation(progress));
+ mYTrans - mYTrans * standardProgress);
+ mBouncerMessageView.setAlpha(standardProgress);
for (int i = 0; i < mViews.length; i++) {
View[] row = mViews[i];
@@ -236,7 +238,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
view.setAlpha(scaledProgress);
int yDistance = mYTrans + mYTransOffset * i;
view.setTranslationY(
- yDistance - (yDistance * standardDecelerate.getInterpolation(progress)));
+ yDistance - (yDistance * standardProgress));
if (view instanceof NumPadAnimationListener) {
((NumPadAnimationListener) view).setProgress(scaledProgress);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 66d5d097ab04..ba5a8c94dc23 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -39,6 +39,7 @@ import static java.lang.Integer.max;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
@@ -1067,10 +1068,14 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation);
+ AnimatorSet anims = new AnimatorSet();
ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation);
- yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE);
- yAnim.setDuration(500);
- yAnim.start();
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
+ 0f);
+
+ anims.setInterpolator(Interpolators.STANDARD_ACCELERATE);
+ anims.playTogether(alphaAnim, yAnim);
+ anims.start();
}
private void setupUserSwitcher() {
@@ -1220,8 +1225,7 @@ public class KeyguardSecurityContainer extends ConstraintLayout {
constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
- constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
- yTrans);
+ constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM);
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index c32b8530d589..2c2caea60f5a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -127,6 +127,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private View.OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event);
private ActivityStarter.OnDismissAction mDismissAction;
private Runnable mCancelAction;
+ private boolean mWillRunDismissFromKeyguard;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -262,8 +263,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
// If there's a pending runnable because the user interacted with a widget
// and we're leaving keyguard, then run it.
boolean deferKeyguardDone = false;
+ mWillRunDismissFromKeyguard = false;
if (mDismissAction != null) {
deferKeyguardDone = mDismissAction.onDismiss();
+ mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
mDismissAction = null;
mCancelAction = null;
}
@@ -526,6 +529,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
/**
+ * @return will the dismissal run from the keyguard layout (instead of from bouncer)
+ */
+ public boolean willRunDismissFromKeyguard() {
+ return mWillRunDismissFromKeyguard;
+ }
+
+ /**
* Remove any dismiss action or cancel action that was set.
*/
public void cancelDismissAction() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 2370c8a27da7..d83af60b1373 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -708,6 +708,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (mKeyguardGoingAway) {
updateFaceListeningState(BIOMETRIC_ACTION_STOP,
FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+ if (cb != null) {
+ cb.onKeyguardGoingAway();
+ }
+ }
}
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
@@ -2433,8 +2439,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
() -> mFaceManager != null && mFaceManager.isHardwareDetected()
&& mFaceManager.hasEnrolledTemplates(userId)
&& mBiometricEnabledForUser.get(userId));
+ if (mIsFaceEnrolled != isFaceEnrolled) {
+ mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled);
+ }
mIsFaceEnrolled = isFaceEnrolled;
- mLogger.logFaceEnrolledUpdated(mIsFaceEnrolled, isFaceEnrolled);
}
public boolean isFaceSupported() {
@@ -3094,12 +3102,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
boolean isUnlockWithFingerprintPossible(int userId) {
// TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
- boolean fpEnrolled = mFpm != null && mFpm.isHardwareDetected()
+ boolean newFpEnrolled = mFpm != null && mFpm.isHardwareDetected()
&& !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId);
- mLogger.logFpEnrolledUpdated(userId,
- mIsUnlockWithFingerprintPossible.getOrDefault(userId, false),
- fpEnrolled);
- mIsUnlockWithFingerprintPossible.put(userId, fpEnrolled);
+ Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
+ if (oldFpEnrolled != newFpEnrolled) {
+ mLogger.logFpEnrolledUpdated(userId, oldFpEnrolled, newFpEnrolled);
+ }
+ mIsUnlockWithFingerprintPossible.put(userId, newFpEnrolled);
return mIsUnlockWithFingerprintPossible.get(userId);
}
@@ -3644,7 +3653,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Register to receive notifications about general keyguard information
* (see {@link KeyguardUpdateMonitorCallback}.
*
- * @param callback The callback to register
+ * @param callback The callback to register. Stay away from passing anonymous instances
+ * as they will likely be dereferenced. Ensure that the callback is a class
+ * field to persist it.
*/
public void registerCallback(KeyguardUpdateMonitorCallback callback) {
Assert.isMainThread();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 0d4889a4c39f..feff216310df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -317,4 +317,9 @@ public class KeyguardUpdateMonitorCallback {
* Called when the non-strong biometric state changed.
*/
public void onNonStrongBiometricAllowedChanged(int userId) { }
+
+ /**
+ * Called when keyguard is going away or not going away.
+ */
+ public void onKeyguardGoingAway() { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
index b9f16665944f..cf5ccc5bbf6c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.controls.management
+import android.annotation.WorkerThread
import android.content.ComponentName
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.util.UserAwareController
@@ -33,6 +34,9 @@ interface ControlsListingController :
*/
fun getCurrentServices(): List<ControlsServiceInfo>
+ @WorkerThread
+ fun forceReload()
+
/**
* Get the app label for a given component.
*
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index c81a2c7e2ac6..8ba060e02c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -16,8 +16,11 @@
package com.android.systemui.controls.management
+import android.annotation.WorkerThread
import android.content.ComponentName
import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
import android.os.UserHandle
import android.service.controls.ControlsProviderService
import android.util.Log
@@ -65,7 +68,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
private val serviceListingBuilder: (Context) -> ServiceListing,
private val userTracker: UserTracker,
dumpManager: DumpManager,
- featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags
) : ControlsListingController, Dumpable {
@Inject
@@ -97,18 +100,7 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
// After here, `list` is not captured, so we don't risk modifying it outside of the callback
backgroundExecutor.execute {
if (userChangeInProgress.get() > 0) return@execute
- if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
- val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
- newServices.forEach {
- it.resolvePanelActivity(allowAllApps) }
- }
-
- if (newServices != availableServices) {
- availableServices = newServices
- callbacks.forEach {
- it.onServicesUpdated(getCurrentServices())
- }
- }
+ updateServices(newServices)
}
}
@@ -120,6 +112,21 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
serviceListing.reload()
}
+ private fun updateServices(newServices: List<ControlsServiceInfo>) {
+ if (featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ val allowAllApps = featureFlags.isEnabled(Flags.APP_PANELS_ALL_APPS_ALLOWED)
+ newServices.forEach {
+ it.resolvePanelActivity(allowAllApps) }
+ }
+
+ if (newServices != availableServices) {
+ availableServices = newServices
+ callbacks.forEach {
+ it.onServicesUpdated(getCurrentServices())
+ }
+ }
+ }
+
override fun changeUser(newUser: UserHandle) {
userChangeInProgress.incrementAndGet()
serviceListing.setListening(false)
@@ -178,6 +185,23 @@ class ControlsListingControllerImpl @VisibleForTesting constructor(
override fun getCurrentServices(): List<ControlsServiceInfo> =
availableServices.map(ControlsServiceInfo::copy)
+ @WorkerThread
+ override fun forceReload() {
+ val packageManager = context.packageManager
+ val intent = Intent(ControlsProviderService.SERVICE_CONTROLS)
+ val user = userTracker.userHandle
+ val flags = PackageManager.GET_SERVICES or
+ PackageManager.GET_META_DATA or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ val services = packageManager.queryIntentServicesAsUser(
+ intent,
+ PackageManager.ResolveInfoFlags.of(flags.toLong()),
+ user
+ ).map { ControlsServiceInfo(userTracker.userContext, it.serviceInfo) }
+ updateServices(services)
+ }
+
/**
* Get the localized label for the component.
*
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 3a4a00c0ccd3..461caccc86d3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -19,6 +19,7 @@ package com.android.systemui.controls.start
import android.content.Context
import android.os.UserHandle
+import androidx.annotation.WorkerThread
import com.android.systemui.CoreStartable
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.dagger.ControlsComponent
@@ -75,11 +76,13 @@ constructor(
// Controls is disabled, we don't need this anymore
return
}
- startForUser()
+ executor.execute(this::startForUser)
userTracker.addCallback(userTrackerCallback, executor)
}
+ @WorkerThread
private fun startForUser() {
+ controlsListingController.forceReload()
selectDefaultPanelIfNecessary()
bindToPanel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cedc226ae0a9..05527bd2c843 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -70,6 +70,8 @@ import com.android.systemui.security.data.repository.SecurityRepositoryModule;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -306,4 +308,8 @@ public abstract class SystemUIModule {
@Binds
abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
+
+ @Binds
+ abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
+ LargeScreenShadeInterpolatorImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index 24e90f066622..aad209090a21 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -61,9 +61,6 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
@VisibleForTesting
boolean mIsAnimationEnabled;
- // Whether dream entry animations are finished.
- private boolean mEntryAnimationsFinished = false;
-
@Inject
protected ComplicationHostViewController(
@Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
@@ -78,14 +75,6 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
mComplicationCollectionViewModel = viewModel;
mDreamOverlayStateController = dreamOverlayStateController;
- mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
- @Override
- public void onStateChanged() {
- mEntryAnimationsFinished =
- mDreamOverlayStateController.areEntryAnimationsFinished();
- }
- });
-
// Whether animations are enabled.
mIsAnimationEnabled = secureSettings.getFloatForUser(
Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f;
@@ -159,7 +148,8 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
// Complications to be added before dream entry animations are finished are set
// to invisible and are animated in.
- if (!mEntryAnimationsFinished && mIsAnimationEnabled) {
+ if (!mDreamOverlayStateController.areEntryAnimationsFinished()
+ && mIsAnimationEnabled) {
view.setVisibility(View.INVISIBLE);
}
mComplications.put(id, viewHolder);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
new file mode 100644
index 000000000000..b20e33a63776
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Restarts the process after all passed in [Condition]s are true. */
+class ConditionalRestarter
+@Inject
+constructor(
+ private val systemExitRestarter: SystemExitRestarter,
+ private val conditions: Set<@JvmSuppressWildcards Condition>,
+ @Named(RESTART_DELAY) private val restartDelaySec: Long,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : Restarter {
+
+ private var restartJob: Job? = null
+ private var pendingReason = ""
+ private var androidRestartRequested = false
+
+ override fun restartSystemUI(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting when idle.")
+ scheduleRestart(reason)
+ }
+
+ override fun restartAndroid(reason: String) {
+ Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when idle.")
+ androidRestartRequested = true
+ scheduleRestart(reason)
+ }
+
+ private fun scheduleRestart(reason: String = "") {
+ pendingReason = if (reason.isEmpty()) pendingReason else reason
+
+ if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
+ if (restartJob == null) {
+ restartJob =
+ applicationScope.launch(backgroundDispatcher) {
+ delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
+ restartNow()
+ }
+ }
+ } else {
+ restartJob?.cancel()
+ restartJob = null
+ }
+ }
+
+ private fun restartNow() {
+ if (androidRestartRequested) {
+ systemExitRestarter.restartAndroid(pendingReason)
+ } else {
+ systemExitRestarter.restartSystemUI(pendingReason)
+ }
+ }
+
+ interface Condition {
+ /**
+ * Should return true if the system is ready to restart.
+ *
+ * A call to this function means that we want to restart and are waiting for this condition
+ * to return true.
+ *
+ * retryFn should be cached if it is _not_ ready to restart, and later called when it _is_
+ * ready to restart. At that point, this method will be called again to verify that the
+ * system is ready.
+ *
+ * Multiple calls to an instance of this method may happen for a single restart attempt if
+ * multiple [Condition]s are being checked. If any one [Condition] returns false, all the
+ * [Condition]s will need to be rechecked on the next restart attempt.
+ */
+ fun canRestartNow(retryFn: () -> Unit): Boolean
+ }
+
+ companion object {
+ const val RESTART_DELAY = "restarter_restart_delay"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
deleted file mode 100644
index a6956a443e46..000000000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.flags
-
-import android.util.Log
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import javax.inject.Inject
-
-/** Restarts SystemUI when the screen is locked. */
-class FeatureFlagsDebugRestarter
-@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val systemExitRestarter: SystemExitRestarter,
-) : Restarter {
-
- private var androidRestartRequested = false
- private var pendingReason = ""
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
- restartNow()
- }
- }
-
- override fun restartSystemUI(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
- Log.i(FeatureFlagsDebug.TAG, reason)
- scheduleRestart(reason)
- }
-
- override fun restartAndroid(reason: String) {
- Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
- androidRestartRequested = true
- scheduleRestart(reason)
- }
-
- fun scheduleRestart(reason: String) {
- pendingReason = reason
- if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
- restartNow()
- } else {
- wakefulnessLifecycle.addObserver(observer)
- }
- }
-
- private fun restartNow() {
- if (androidRestartRequested) {
- systemExitRestarter.restartAndroid(pendingReason)
- } else {
- systemExitRestarter.restartSystemUI(pendingReason)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
deleted file mode 100644
index c08266caf147..000000000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.flags
-
-import android.util.Log
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-
-/** Restarts SystemUI when the device appears idle. */
-class FeatureFlagsReleaseRestarter
-@Inject
-constructor(
- private val wakefulnessLifecycle: WakefulnessLifecycle,
- private val batteryController: BatteryController,
- @Background private val bgExecutor: DelayableExecutor,
- private val systemExitRestarter: SystemExitRestarter
-) : Restarter {
- var listenersAdded = false
- var pendingRestart: Runnable? = null
- private var pendingReason = ""
- var androidRestartRequested = false
-
- val observer =
- object : WakefulnessLifecycle.Observer {
- override fun onFinishedGoingToSleep() {
- scheduleRestart(pendingReason)
- }
- }
-
- val batteryCallback =
- object : BatteryController.BatteryStateChangeCallback {
- override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
- scheduleRestart(pendingReason)
- }
- }
-
- override fun restartSystemUI(reason: String) {
- Log.d(
- FeatureFlagsDebug.TAG,
- "SystemUI Restart requested. Restarting when plugged in and idle."
- )
- scheduleRestart(reason)
- }
-
- override fun restartAndroid(reason: String) {
- Log.d(
- FeatureFlagsDebug.TAG,
- "Android Restart requested. Restarting when plugged in and idle."
- )
- androidRestartRequested = true
- scheduleRestart(reason)
- }
-
- private fun scheduleRestart(reason: String) {
- // Don't bother adding listeners twice.
- pendingReason = reason
- if (!listenersAdded) {
- listenersAdded = true
- wakefulnessLifecycle.addObserver(observer)
- batteryController.addCallback(batteryCallback)
- }
- if (
- wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
- ) {
- if (pendingRestart == null) {
- pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
- }
- } else if (pendingRestart != null) {
- pendingRestart?.run()
- pendingRestart = null
- }
- }
-
- private fun restartNow() {
- Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
- if (androidRestartRequested) {
- systemExitRestarter.restartAndroid(pendingReason)
- } else {
- systemExitRestarter.restartSystemUI(pendingReason)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6d0d893a60f2..8b66b00e8670 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -117,6 +117,9 @@ object Flags {
val ANIMATED_NOTIFICATION_SHADE_INSETS =
unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
+ // TODO(b/268005230): Tracking Bug
+ @JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim")
+
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -140,7 +143,7 @@ object Flags {
* the digits when the clock moves.
*/
@JvmField
- val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation", teamfood = true)
+ val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation")
/**
* Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
@@ -218,6 +221,11 @@ object Flags {
teamfood = true,
)
+ /** Whether to inflate the bouncer view on a background thread. */
+ // TODO(b/272091103): Tracking Bug
+ @JvmField
+ val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true)
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -388,13 +396,16 @@ object Flags {
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
// TODO(b/270882464): Tracking Bug
- val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2")
+ val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true)
// TODO(b/265045965): Tracking Bug
val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
@JvmField
- val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(1004, "enable_low_light_clock_undocked")
+ // TODO(b/271428141): Tracking Bug
+ val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(
+ 1004,
+ "enable_low_light_clock_undocked", teamfood = true)
// 1100 - windowing
@Keep
@@ -482,6 +493,13 @@ object Flags {
val ENABLE_PIP_APP_ICON_OVERLAY =
sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+ // TODO(b/272110828): Tracking bug
+ @Keep
+ @JvmField
+ val ENABLE_MOVE_FLOATING_WINDOW_IN_TABLETOP =
+ sysPropBooleanFlag(
+ 1116, "persist.wm.debug.enable_move_floating_window_in_tabletop", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
@@ -590,7 +608,7 @@ object Flags {
@JvmField
val LEAVE_SHADE_OPEN_FOR_BUGREPORT = releasedFlag(1800, "leave_shade_open_for_bugreport")
// TODO(b/265944639): Tracking Bug
- @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
+ @JvmField val DUAL_SHADE = unreleasedFlag(1801, "dual_shade")
// 1900
@JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
@@ -662,4 +680,9 @@ object Flags {
// TODO(b/259428678): Tracking Bug
@JvmField
val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator")
+
+ // TODO(b/272036292): Tracking Bug
+ @JvmField
+ val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
+ unreleasedFlag(2602, "large_shade_granular_alpha_interpolation", teamfood = true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 0054d266c283..3c5012559a89 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.flags
+import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Named
@@ -22,6 +23,8 @@ import javax.inject.Named
/** Module containing shared code for all FeatureFlag implementations. */
@Module
interface FlagsCommonModule {
+ @Binds fun bindsRestarter(impl: ConditionalRestarter): Restarter
+
companion object {
const val ALL_FLAGS = "all_flags"
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
new file mode 100644
index 000000000000..3120638cb17f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Returns true when the device is plugged in. */
+class PluggedInCondition
+@Inject
+constructor(
+ private val batteryController: BatteryController,
+) : ConditionalRestarter.Condition {
+
+ var listenersAdded = false
+ var retryFn: (() -> Unit)? = null
+
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+ retryFn?.invoke()
+ }
+ }
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ if (!listenersAdded) {
+ listenersAdded = true
+ batteryController.addCallback(batteryCallback)
+ }
+
+ this.retryFn = retryFn
+
+ return batteryController.isPluggedIn
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
new file mode 100644
index 000000000000..49e61afbdcd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class ScreenIdleCondition
+@Inject
+constructor(
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
+) : ConditionalRestarter.Condition {
+
+ var listenersAdded = false
+ var retryFn: (() -> Unit)? = null
+
+ val observer =
+ object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ retryFn?.invoke()
+ }
+ }
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ if (!listenersAdded) {
+ listenersAdded = true
+ wakefulnessLifecycle.addObserver(observer)
+ }
+
+ this.retryFn = retryFn
+
+ return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
index 85d0379a77db..5e806b612805 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
@@ -46,8 +46,15 @@ constructor(
viewModel.dialogContent.collect { dialogViewModel ->
if (dialogViewModel != null) {
if (dialog == null) {
- dialog = KeyboardBacklightDialog(context, dialogViewModel)
- // pass viewModel and show
+ dialog =
+ KeyboardBacklightDialog(
+ context,
+ initialCurrentLevel = dialogViewModel.currentValue,
+ initialMaxLevel = dialogViewModel.maxValue
+ )
+ dialog?.show()
+ } else {
+ dialog?.updateState(dialogViewModel.currentValue, dialogViewModel.maxValue)
}
} else {
dialog?.dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index b68a2a84b5d1..a173f8b914db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -17,16 +17,260 @@
package com.android.systemui.keyboard.backlight.ui.view
+import android.annotation.ColorInt
import android.app.Dialog
import android.content.Context
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
import android.os.Bundle
-import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogContentViewModel
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.LinearLayout.LayoutParams
+import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import com.android.systemui.R
+import com.android.systemui.util.children
-class KeyboardBacklightDialog(context: Context, val viewModel: BacklightDialogContentViewModel) :
- Dialog(context) {
+class KeyboardBacklightDialog(
+ context: Context,
+ initialCurrentLevel: Int,
+ initialMaxLevel: Int,
+) : Dialog(context) {
+
+ private data class RootProperties(
+ val cornerRadius: Float,
+ val verticalPadding: Int,
+ val horizontalPadding: Int,
+ )
+
+ private data class BacklightIconProperties(
+ val width: Int,
+ val height: Int,
+ val leftMargin: Int,
+ )
+
+ private data class StepViewProperties(
+ val width: Int,
+ val height: Int,
+ val horizontalMargin: Int,
+ val smallRadius: Float,
+ val largeRadius: Float,
+ )
+
+ private var currentLevel: Int = 0
+ private var maxLevel: Int = 0
+
+ private lateinit var rootView: LinearLayout
+
+ private var dialogBottomMargin = 208
+ private lateinit var rootProperties: RootProperties
+ private lateinit var iconProperties: BacklightIconProperties
+ private lateinit var stepProperties: StepViewProperties
+ @ColorInt var filledRectangleColor: Int = 0
+ @ColorInt var emptyRectangleColor: Int = 0
+ @ColorInt var backgroundColor: Int = 0
+
+ init {
+ currentLevel = initialCurrentLevel
+ maxLevel = initialMaxLevel
+ }
override fun onCreate(savedInstanceState: Bundle?) {
+ setUpWindowProperties(this)
+ setWindowTitle()
+ updateResources()
+ rootView = buildRootView()
+ setContentView(rootView)
super.onCreate(savedInstanceState)
- // TODO(b/268650355) Implement the dialog
+ updateState(currentLevel, maxLevel, forceRefresh = true)
+ }
+
+ private fun updateResources() {
+ context.resources.apply {
+ filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
+ emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
+ backgroundColor = getColor(R.color.backlight_indicator_background)
+ rootProperties =
+ RootProperties(
+ cornerRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_corner_radius)
+ .toFloat(),
+ verticalPadding =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_vertical_padding),
+ horizontalPadding =
+ getDimensionPixelSize(R.dimen.backlight_indicator_root_horizontal_padding)
+ )
+ iconProperties =
+ BacklightIconProperties(
+ width = getDimensionPixelSize(R.dimen.backlight_indicator_icon_width),
+ height = getDimensionPixelSize(R.dimen.backlight_indicator_icon_height),
+ leftMargin =
+ getDimensionPixelSize(R.dimen.backlight_indicator_icon_left_margin),
+ )
+ stepProperties =
+ StepViewProperties(
+ width = getDimensionPixelSize(R.dimen.backlight_indicator_step_width),
+ height = getDimensionPixelSize(R.dimen.backlight_indicator_step_height),
+ horizontalMargin =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_horizontal_margin),
+ smallRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_small_radius)
+ .toFloat(),
+ largeRadius =
+ getDimensionPixelSize(R.dimen.backlight_indicator_step_large_radius)
+ .toFloat(),
+ )
+ }
+ }
+
+ fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
+ if (maxLevel != max || forceRefresh) {
+ maxLevel = max
+ rootView.removeAllViews()
+ buildStepViews().forEach { rootView.addView(it) }
+ }
+ currentLevel = current
+ updateLevel()
+ }
+
+ private fun updateLevel() {
+ rootView.children.forEachIndexed(
+ action = { index, v ->
+ val drawable = v.background as ShapeDrawable
+ if (index <= currentLevel) {
+ updateColor(drawable, filledRectangleColor)
+ } else {
+ updateColor(drawable, emptyRectangleColor)
+ }
+ }
+ )
+ }
+
+ private fun updateColor(drawable: ShapeDrawable, @ColorInt color: Int) {
+ if (drawable.paint.color != color) {
+ drawable.paint.color = color
+ drawable.invalidateSelf()
+ }
+ }
+
+ private fun buildRootView(): LinearLayout {
+ val linearLayout =
+ LinearLayout(context).apply {
+ orientation = LinearLayout.HORIZONTAL
+ layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+ setPadding(
+ /* left= */ rootProperties.horizontalPadding,
+ /* top= */ rootProperties.verticalPadding,
+ /* right= */ rootProperties.horizontalPadding,
+ /* bottom= */ rootProperties.verticalPadding
+ )
+ }
+ val drawable =
+ ShapeDrawable(
+ RoundRectShape(
+ /* outerRadii= */ FloatArray(8) { rootProperties.cornerRadius },
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ )
+ drawable.paint.color = backgroundColor
+ linearLayout.background = drawable
+ return linearLayout
+ }
+
+ private fun buildStepViews(): List<FrameLayout> {
+ val stepViews = (0..maxLevel).map { i -> createStepViewAt(i) }
+ stepViews[0].addView(createBacklightIconView())
+ return stepViews
+ }
+
+ private fun createStepViewAt(i: Int): FrameLayout {
+ return FrameLayout(context).apply {
+ layoutParams =
+ FrameLayout.LayoutParams(stepProperties.width, stepProperties.height).apply {
+ setMargins(
+ /* left= */ stepProperties.horizontalMargin,
+ /* top= */ 0,
+ /* right= */ stepProperties.horizontalMargin,
+ /* bottom= */ 0
+ )
+ }
+ val drawable =
+ ShapeDrawable(
+ RoundRectShape(
+ /* outerRadii= */ radiiForIndex(i, maxLevel),
+ /* inset= */ null,
+ /* innerRadii= */ null
+ )
+ )
+ drawable.paint.color = emptyRectangleColor
+ background = drawable
+ }
+ }
+
+ private fun createBacklightIconView(): ImageView {
+ return ImageView(context).apply {
+ setImageResource(R.drawable.ic_keyboard_backlight)
+ layoutParams =
+ FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
+ gravity = Gravity.CENTER
+ leftMargin = iconProperties.leftMargin
+ }
+ }
+ }
+
+ private fun setWindowTitle() {
+ val attrs = window.attributes
+ // TODO(b/271796169): check if title needs to be a translatable resource.
+ attrs.title = "KeyboardBacklightDialog"
+ attrs?.y = dialogBottomMargin
+ window.attributes = attrs
+ }
+
+ private fun setUpWindowProperties(dialog: Dialog) {
+ val window = dialog.window
+ window.requestFeature(Window.FEATURE_NO_TITLE) // otherwise fails while creating actionBar
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ window.addFlags(
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ )
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ window.setBackgroundDrawableResource(android.R.color.transparent)
+ window.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+ setCanceledOnTouchOutside(true)
+ }
+
+ private fun radiiForIndex(i: Int, last: Int): FloatArray {
+ val smallRadius = stepProperties.smallRadius
+ val largeRadius = stepProperties.largeRadius
+ return when (i) {
+ 0 -> // left radii bigger
+ floatArrayOf(
+ largeRadius,
+ largeRadius,
+ smallRadius,
+ smallRadius,
+ smallRadius,
+ smallRadius,
+ largeRadius,
+ largeRadius
+ )
+ last -> // right radii bigger
+ floatArrayOf(
+ smallRadius,
+ smallRadius,
+ largeRadius,
+ largeRadius,
+ largeRadius,
+ largeRadius,
+ smallRadius,
+ smallRadius
+ )
+ else -> FloatArray(8) { smallRadius } // all radii equal
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 8ae171f9264f..90562dc4a243 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -380,10 +380,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
// If the launcher is underneath, but we're about to launch an activity, don't do
// the animations since they won't be visible.
!notificationShadeWindowController.isLaunchingActivity &&
- launcherUnlockController != null &&
- // Temporarily disable for foldables since foldable launcher has two first pages,
- // which breaks the in-window animation.
- !isFoldable(context)
+ launcherUnlockController != null
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 85554ac9ae44..2815df68ad0a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -964,13 +964,24 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ if (!handleOnAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback)) {
+ // Usually we rely on animation completion to synchronize occluded status,
+ // but there was no animation to play, so just update it now.
+ setOccluded(true /* isOccluded */, false /* animate */);
+ }
+ }
+
+ private boolean handleOnAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
if (apps == null || apps.length == 0 || apps[0] == null) {
if (DEBUG) {
Log.d(TAG, "No apps provided to the OccludeByDream runner; "
+ "skipping occluding animation.");
}
finishedCallback.onAnimationFinished();
- return;
+ return false;
}
final RemoteAnimationTarget primary = apps[0];
@@ -980,7 +991,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Log.w(TAG, "The occluding app isn't Dream; "
+ "finishing up. Please check that the config is correct.");
finishedCallback.onAnimationFinished();
- return;
+ return false;
}
final SyncRtSurfaceTransactionApplier applier =
@@ -1029,6 +1040,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mOccludeByDreamAnimator.start();
});
+ return true;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index faeb48526ae4..a2589d3d4116 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -52,6 +52,7 @@ interface BouncerViewDelegate {
cancelAction: Runnable?,
)
fun willDismissWithActions(): Boolean
+ fun willRunDismissFromKeyguard(): Boolean
/** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
fun getBackCallback(): OnBackAnimationCallback
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 911861ddde47..28cc69758308 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -64,7 +64,11 @@ constructor(
.sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
.collect { pair ->
val (isAbleToDream, lastStartedTransition) = pair
- if (isAbleToDream && lastStartedTransition.to == KeyguardState.LOCKSCREEN) {
+ if (
+ isAbleToDream &&
+ lastStartedTransition.to == KeyguardState.LOCKSCREEN &&
+ lastStartedTransition.from != KeyguardState.AOD
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2dc8fee25379..1fbfff95ab7e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -47,6 +47,7 @@ constructor(
listenForOccludedToLockscreen()
listenForOccludedToDreaming()
listenForOccludedToAodOrDozing()
+ listenForOccludedToGone()
}
private fun listenForOccludedToDreaming() {
@@ -72,11 +73,22 @@ constructor(
private fun listenForOccludedToLockscreen() {
scope.launch {
keyguardInteractor.isKeyguardOccluded
- .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
- .collect { (isOccluded, lastStartedKeyguardState) ->
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
// Occlusion signals come from the framework, and should interrupt any
// existing transition
- if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
+ if (
+ !isOccluded &&
+ isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
name,
@@ -90,6 +102,38 @@ constructor(
}
}
+ private fun listenForOccludedToGone() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(
+ combine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (
+ !isOccluded &&
+ !isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ name,
+ KeyguardState.OCCLUDED,
+ KeyguardState.GONE,
+ getAnimator(),
+ )
+ )
+ }
+ }
+ }
+ }
+
private fun listenForOccludedToAodOrDozing() {
scope.launch {
keyguardInteractor.wakefulnessModel
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index ec99049b42e3..c42e5028e18c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -142,6 +142,8 @@ constructor(
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /** Whether or not quick settings or quick quick settings are showing. */
+ val isQuickSettingsVisible: Flow<Boolean> = repository.isQuickSettingsVisible
/**
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
* side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 568cc0f42639..66f87bade308 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -93,8 +93,9 @@ constructor(
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
- ) { affordance, isDozing, isKeyguardShowing ->
- if (!isDozing && isKeyguardShowing) {
+ keyguardInteractor.isQuickSettingsVisible
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible ->
+ if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index c709fd18298c..a263562b5a7e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -122,21 +122,24 @@ constructor(
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
+ /**
+ * This callback needs to be a class field so it does not get garbage collected.
+ */
+ val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricRunningStateChanged(
+ running: Boolean,
+ biometricSourceType: BiometricSourceType?
+ ) {
+ updateSideFpsVisibility()
+ }
+
+ override fun onStrongAuthStateChanged(userId: Int) {
+ updateSideFpsVisibility()
+ }
+ }
+
init {
- keyguardUpdateMonitor.registerCallback(
- object : KeyguardUpdateMonitorCallback() {
- override fun onBiometricRunningStateChanged(
- running: Boolean,
- biometricSourceType: BiometricSourceType?
- ) {
- updateSideFpsVisibility()
- }
-
- override fun onStrongAuthStateChanged(userId: Int) {
- updateSideFpsVisibility()
- }
- }
- )
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
}
// TODO(b/243685699): Move isScrimmed logic to data layer.
@@ -377,6 +380,11 @@ constructor(
return primaryBouncerView.delegate?.willDismissWithActions() == true
}
+ /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
+ fun willRunDismissFromKeyguard(): Boolean {
+ return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
+ }
+
/** Returns whether the bouncer should be full screen. */
private fun needsFullscreenBouncer(): Boolean {
val mode: KeyguardSecurityModel.SecurityMode =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
new file mode 100644
index 000000000000..1db77336109e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.keyguard.shared.model
+
+/** Alpha values for scrim updates */
+data class ScrimAlpha(
+ val frontAlpha: Float = 0f,
+ val behindAlpha: Float = 0f,
+ val notificationsAlpha: Float = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 2337ffc35fa6..bb617bd50c69 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -97,6 +97,10 @@ object KeyguardBouncerViewBinder {
override fun willDismissWithActions(): Boolean {
return securityContainerController.hasDismissActions()
}
+
+ override fun willRunDismissFromKeyguard(): Boolean {
+ return securityContainerController.willRunDismissFromKeyguard()
+ }
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 92038e24edf3..b23247c30256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -20,11 +20,14 @@ import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.statusbar.SysuiStatusBarStateController
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
/**
* Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
@@ -36,6 +39,7 @@ class PrimaryBouncerToGoneTransitionViewModel
constructor(
private val interactor: KeyguardTransitionInteractor,
private val statusBarStateController: SysuiStatusBarStateController,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
) {
private val transitionAnimation =
KeyguardTransitionAnimationFlow(
@@ -44,26 +48,49 @@ constructor(
)
private var leaveShadeOpen: Boolean = false
+ private var willRunDismissFromKeyguard: Boolean = false
/** Bouncer container alpha */
val bouncerAlpha: Flow<Float> =
transitionAnimation.createFlow(
duration = 200.milliseconds,
- onStep = { 1f - it },
- )
-
- /** Scrim behind alpha */
- val scrimBehindAlpha: Flow<Float> =
- transitionAnimation.createFlow(
- duration = TO_GONE_DURATION,
- interpolator = EMPHASIZED_ACCELERATE,
- onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() },
+ onStart = {
+ willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
onStep = {
- if (leaveShadeOpen) {
- 1f
+ if (willRunDismissFromKeyguard) {
+ 0f
} else {
1f - it
}
},
)
+
+ /** Scrim alpha values */
+ val scrimAlpha: Flow<ScrimAlpha> =
+ transitionAnimation
+ .createFlow(
+ duration = TO_GONE_DURATION,
+ interpolator = EMPHASIZED_ACCELERATE,
+ onStart = {
+ leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+ willRunDismissFromKeyguard =
+ primaryBouncerInteractor.willRunDismissFromKeyguard()
+ },
+ onStep = { 1f - it },
+ )
+ .map {
+ if (willRunDismissFromKeyguard) {
+ ScrimAlpha(
+ notificationsAlpha = 1f,
+ )
+ } else if (leaveShadeOpen) {
+ ScrimAlpha(
+ behindAlpha = 1f,
+ notificationsAlpha = 1f,
+ )
+ } else {
+ ScrimAlpha(behindAlpha = it)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
index 4880f80e7716..b73ddc50f831 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableChange.kt
@@ -59,6 +59,17 @@ data class TableChange(
int = value
}
+ /** Updates this to store the same value as [change]. */
+ fun updateTo(change: TableChange) {
+ reset(change.timestamp, change.columnPrefix, change.columnName)
+ when (change.type) {
+ DataType.STRING -> set(change.str)
+ DataType.INT -> set(change.int)
+ DataType.BOOLEAN -> set(change.bool)
+ DataType.EMPTY -> {}
+ }
+ }
+
/** Returns true if this object has a change. */
fun hasData(): Boolean {
return columnName.isNotBlank() && type != DataType.EMPTY
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 29f273a5ed41..a0f1c959aed6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -16,13 +16,13 @@
package com.android.systemui.log.table
+import android.os.Trace
import com.android.systemui.Dumpable
import com.android.systemui.plugins.util.RingBuffer
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import java.text.SimpleDateFormat
import java.util.Locale
-import kotlinx.coroutines.flow.Flow
/**
* A logger that logs changes in table format.
@@ -82,6 +82,19 @@ class TableLogBuffer(
private val buffer = RingBuffer(maxSize) { TableChange() }
+ // Stores the most recently evicted value for each column name (indexed on column name).
+ //
+ // Why it's necessary: Because we use a RingBuffer of a fixed size, it's possible that a column
+ // that's logged infrequently will eventually get pushed out by a different column that's
+ // logged more frequently. Then, that infrequently-logged column isn't present in the RingBuffer
+ // at all and we have no logs that the column ever existed. This is a problem because the
+ // column's information is still relevant, valid, and may be critical to debugging issues.
+ //
+ // Fix: When a change is being evicted from the RingBuffer, we store it in this map (based on
+ // its [TableChange.getName()]. This ensures that we always have at least one value for every
+ // column ever logged. See b/272016422 for more details.
+ private val lastEvictedValues = mutableMapOf<String, TableChange>()
+
// A [TableRowLogger] object, re-used each time [logDiffs] is called.
// (Re-used to avoid object allocation.)
private val tempRow = TableRowLoggerImpl(0, columnPrefix = "", this)
@@ -138,18 +151,24 @@ class TableLogBuffer(
// timestamps.)
private fun logChange(timestamp: Long, prefix: String, columnName: String, value: String?) {
+ Trace.beginSection("TableLogBuffer#logChange(string)")
val change = obtain(timestamp, prefix, columnName)
change.set(value)
+ Trace.endSection()
}
private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Boolean) {
+ Trace.beginSection("TableLogBuffer#logChange(boolean)")
val change = obtain(timestamp, prefix, columnName)
change.set(value)
+ Trace.endSection()
}
private fun logChange(timestamp: Long, prefix: String, columnName: String, value: Int?) {
+ Trace.beginSection("TableLogBuffer#logChange(int)")
val change = obtain(timestamp, prefix, columnName)
change.set(value)
+ Trace.endSection()
}
// TODO(b/259454430): Add additional change types here.
@@ -158,6 +177,9 @@ class TableLogBuffer(
private fun obtain(timestamp: Long, prefix: String, columnName: String): TableChange {
verifyValidName(prefix, columnName)
val tableChange = buffer.advance()
+ if (tableChange.hasData()) {
+ saveEvictedValue(tableChange)
+ }
tableChange.reset(timestamp, prefix, columnName)
return tableChange
}
@@ -173,10 +195,23 @@ class TableLogBuffer(
}
}
+ private fun saveEvictedValue(change: TableChange) {
+ Trace.beginSection("TableLogBuffer#saveEvictedValue")
+ val name = change.getName()
+ val previouslyEvicted =
+ lastEvictedValues[name] ?: TableChange().also { lastEvictedValues[name] = it }
+ // For recycling purposes, update the existing object in the map with the new information
+ // instead of creating a new object.
+ previouslyEvicted.updateTo(change)
+ Trace.endSection()
+ }
+
@Synchronized
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println(HEADER_PREFIX + name)
pw.println("version $VERSION")
+
+ lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
for (i in 0 until buffer.size) {
buffer[i].dump(pw)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 6023bc250b1b..525b2fcb8dbc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -1343,13 +1343,9 @@ class MediaDataManager(
fun onNotificationRemoved(key: String) {
Assert.isMainThread()
val removed = mediaEntries.remove(key) ?: return
- val isEligibleForResume =
- removed.isLocalSession() ||
- (mediaFlags.isRemoteResumeAllowed() &&
- removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
- } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) {
+ } else if (isAbleToResume(removed)) {
convertToResumePlayer(key, removed)
} else if (mediaFlags.isRetainingPlayersEnabled()) {
handlePossibleRemoval(key, removed, notificationRemoved = true)
@@ -1369,6 +1365,14 @@ class MediaDataManager(
handlePossibleRemoval(key, updated)
}
+ private fun isAbleToResume(data: MediaData): Boolean {
+ val isEligibleForResume =
+ data.isLocalSession() ||
+ (mediaFlags.isRemoteResumeAllowed() &&
+ data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
+ return useMediaResumption && data.resumeAction != null && isEligibleForResume
+ }
+
/**
* Convert to resume state if the player is no longer valid and active, then notify listeners
* that the data was updated. Does not convert to resume state if the player is still valid, or
@@ -1391,8 +1395,9 @@ class MediaDataManager(
if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key")
mediaEntries.put(key, removed)
notifyMediaDataLoaded(key, key, removed)
- } else if (removed.active) {
- // This player was still active - it didn't last long enough to time out: remove
+ } else if (removed.active && !isAbleToResume(removed)) {
+ // This player was still active - it didn't last long enough to time out,
+ // and its app doesn't normally support resume: remove
if (DEBUG) Log.d(TAG, "Removing still-active player $key")
notifyMediaDataRemoved(key)
logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 92e0c851a462..b0389b50cd7d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -239,6 +239,8 @@ constructor(
data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) {
// TODO also check for a media button receiver intended for restarting (b/154127084)
+ // Set null action to prevent additional attempts to connect
+ mediaDataManager.setResumeAction(key, null)
Log.d(TAG, "Checking for service component for " + data.packageName)
val pm = context.packageManager
val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
@@ -249,9 +251,6 @@ constructor(
backgroundExecutor.execute {
tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
}
- } else {
- // No service found
- mediaDataManager.setResumeAction(key, null)
}
}
}
@@ -263,8 +262,6 @@ constructor(
*/
private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
Log.d(TAG, "Testing if we can connect to $componentName")
- // Set null action to prevent additional attempts to connect
- mediaDataManager.setResumeAction(key, null)
mediaBrowser =
mediaBrowserFactory.create(
object : ResumeMediaBrowser.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
index 3493b2453fd6..d460b5b5d782 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
@@ -85,16 +85,13 @@ public class ResumeMediaBrowser {
* ResumeMediaBrowser#disconnect will be called automatically with this function.
*/
public void findRecentMedia() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(
+ MediaBrowser browser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "findRecentMedia");
- mMediaBrowser.connect();
+ connectBrowser(browser, "findRecentMedia");
}
private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
@@ -202,6 +199,21 @@ public class ResumeMediaBrowser {
};
/**
+ * Connect using a new media browser. Disconnects the existing browser first, if it exists.
+ * @param browser media browser to connect
+ * @param reason Reason to log for connection
+ */
+ private void connectBrowser(MediaBrowser browser, String reason) {
+ mLogger.logConnection(mComponentName, reason);
+ disconnect();
+ mMediaBrowser = browser;
+ if (browser != null) {
+ browser.connect();
+ }
+ updateMediaController();
+ }
+
+ /**
* Disconnect the media browser. This should be done after callbacks have completed to
* disconnect from the media browser service.
*/
@@ -222,10 +234,9 @@ public class ResumeMediaBrowser {
* getting a media update from the app
*/
public void restart() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(mComponentName,
+ MediaBrowser browser = mBrowserFactory.create(mComponentName,
new MediaBrowser.ConnectionCallback() {
@Override
public void onConnected() {
@@ -265,9 +276,7 @@ public class ResumeMediaBrowser {
disconnect();
}
}, rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "restart");
- mMediaBrowser.connect();
+ connectBrowser(browser, "restart");
}
@VisibleForTesting
@@ -305,16 +314,13 @@ public class ResumeMediaBrowser {
* ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
*/
public void testConnection() {
- disconnect();
Bundle rootHints = new Bundle();
rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
- mMediaBrowser = mBrowserFactory.create(
+ MediaBrowser browser = mBrowserFactory.create(
mComponentName,
mConnectionCallback,
rootHints);
- updateMediaController();
- mLogger.logConnection(mComponentName, "testConnection");
- mMediaBrowser.connect();
+ connectBrowser(browser, "testConnection");
}
/** Updates mMediaController based on our current browser values. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 9d34df8c1eb8..938a9c35c205 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -51,6 +51,8 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.compose.ComposeFacade;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
@@ -60,6 +62,7 @@ import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -112,6 +115,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private final MediaHost mQqsMediaHost;
private final QSFragmentComponent.Factory mQsComponentFactory;
private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
private final QSLogger mLogger;
private final FooterActionsController mFooterActionsController;
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
@@ -159,12 +164,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
// visible;
private boolean mQsVisible;
- /**
- * Whether the notification panel uses the full width of the screen.
- *
- * Usually {@code true} on small screens, and {@code false} on large screens.
- */
- private boolean mIsNotificationPanelFullWidth;
+ private boolean mIsSmallScreen;
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -176,13 +176,17 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
DumpManager dumpManager, QSLogger qsLogger,
FooterActionsController footerActionsController,
- FooterActionsViewModel.Factory footerActionsViewModelFactory) {
+ FooterActionsViewModel.Factory footerActionsViewModelFactory,
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ FeatureFlags featureFlags) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
mQsComponentFactory = qsComponentFactory;
mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger;
mLogger = qsLogger;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
commandQueue.observe(getLifecycle(), this);
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
@@ -607,7 +611,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
@Override
public void setIsNotificationPanelFullWidth(boolean isFullWidth) {
- mIsNotificationPanelFullWidth = isFullWidth;
+ mIsSmallScreen = isFullWidth;
}
@Override
@@ -710,7 +714,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
private float calculateAlphaProgress(float panelExpansionFraction) {
- if (mIsNotificationPanelFullWidth) {
+ if (mIsSmallScreen) {
// Small screens. QS alpha is not animated.
return 1;
}
@@ -745,7 +749,12 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
// Alpha progress should be linear on lockscreen shade expansion.
return progress;
}
- return ShadeInterpolation.getContentAlpha(progress);
+ if (mIsSmallScreen || !mFeatureFlags.isEnabled(
+ Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(progress);
+ } else {
+ return mLargeScreenShadeInterpolator.getQsAlpha(progress);
+ }
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index c8c133774766..7cfe2327f992 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -57,7 +57,8 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
-class ImageExporter {
+/** A class to help with exporting screenshot to storage. */
+public class ImageExporter {
private static final String TAG = LogConfig.logTag(ImageExporter.class);
static final Duration PENDING_ENTRY_TTL = Duration.ofHours(24);
@@ -90,7 +91,7 @@ class ImageExporter {
private final FeatureFlags mFlags;
@Inject
- ImageExporter(ContentResolver resolver, FeatureFlags flags) {
+ public ImageExporter(ContentResolver resolver, FeatureFlags flags) {
mResolver = resolver;
mFlags = flags;
}
@@ -148,7 +149,7 @@ class ImageExporter {
*
* @return a listenable future result
*/
- ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
+ public ListenableFuture<Result> export(Executor executor, UUID requestId, Bitmap bitmap,
UserHandle owner) {
return export(executor, requestId, bitmap, ZonedDateTime.now(), owner);
}
@@ -181,13 +182,14 @@ class ImageExporter {
);
}
- static class Result {
- Uri uri;
- UUID requestId;
- String fileName;
- long timestamp;
- CompressFormat format;
- boolean published;
+ /** The result returned by the task exporting screenshots to storage. */
+ public static class Result {
+ public Uri uri;
+ public UUID requestId;
+ public String fileName;
+ public long timestamp;
+ public CompressFormat format;
+ public boolean published;
@Override
public String toString() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 8721d71897f7..557e95c64443 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -419,6 +419,10 @@ public class ScreenshotController {
return;
}
+ mScreenBitmap = screenshot.getBitmap();
+ String oldPackageName = mPackageName;
+ mPackageName = screenshot.getPackageNameString();
+
if (!isUserSetupComplete(Process.myUserHandle())) {
Log.w(TAG, "User setup not complete, displaying toast only");
// User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
@@ -433,10 +437,6 @@ public class ScreenshotController {
mScreenshotTakenInPortrait =
mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
- String oldPackageName = mPackageName;
- mPackageName = screenshot.getPackageNameString();
-
- mScreenBitmap = screenshot.getBitmap();
// Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index fc94aed5336a..7a62bae5b5ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -93,13 +93,7 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "User has discarded the result of a long screenshot")
SCREENSHOT_LONG_SCREENSHOT_EXIT(911),
@UiEvent(doc = "A screenshot has been taken and saved to work profile")
- SCREENSHOT_SAVED_TO_WORK_PROFILE(1240),
- @UiEvent(doc = "Notes application triggered the screenshot for notes")
- SCREENSHOT_FOR_NOTE_TRIGGERED(1308),
- @UiEvent(doc = "User accepted the screenshot to be sent to the notes app")
- SCREENSHOT_FOR_NOTE_ACCEPTED(1309),
- @UiEvent(doc = "User cancelled the screenshot for notes app flow")
- SCREENSHOT_FOR_NOTE_CANCELLED(1310);
+ SCREENSHOT_SAVED_TO_WORK_PROFILE(1240);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 8323d1e6bae3..c44f4f391831 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2883,7 +2883,10 @@ public final class NotificationPanelViewController implements Dumpable {
mHeadsUpStartHeight = startHeight;
float scrimMinFraction;
if (mSplitShadeEnabled) {
- boolean highHun = mHeadsUpStartHeight * 2.5 > mSplitShadeScrimTransitionDistance;
+ boolean highHun = mHeadsUpStartHeight * 2.5
+ >
+ (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
+ ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
// if HUN height is higher than 40% of predefined transition distance, it means HUN
// is too high for regular transition. In that case we need to calculate transition
// distance - here we take scrim transition distance as equal to shade transition
@@ -4650,13 +4653,8 @@ public final class NotificationPanelViewController implements Dumpable {
gesture possible. */
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
- if (mTrackingPointer < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- } else {
- mShadeLog.logMotionEvent(event, "Skipping intercept of multitouch pointer");
- return false;
- }
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
}
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt
new file mode 100644
index 000000000000..05191317e86b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.transition
+
+import android.util.MathUtils
+import com.android.systemui.animation.ShadeInterpolation
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when in portrait on a large screen. */
+internal class LargeScreenPortraitShadeInterpolator @Inject internal constructor() :
+ LargeScreenShadeInterpolator {
+
+ override fun getBehindScrimAlpha(fraction: Float): Float {
+ return MathUtils.constrainedMap(0f, 1f, 0f, 0.3f, fraction)
+ }
+
+ override fun getNotificationScrimAlpha(fraction: Float): Float {
+ return MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, fraction)
+ }
+
+ override fun getNotificationContentAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+
+ override fun getNotificationFooterAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+
+ override fun getQsAlpha(fraction: Float): Float {
+ return ShadeInterpolation.getContentAlpha(fraction)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt
new file mode 100644
index 000000000000..671dfc9c80ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.transition
+
+/** An interpolator interface for the shade expansion. */
+interface LargeScreenShadeInterpolator {
+
+ /** Returns the alpha for the behind/back scrim. */
+ fun getBehindScrimAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notification scrim. */
+ fun getNotificationScrimAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notifications. */
+ fun getNotificationContentAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the notifications footer (Manager, Clear All). */
+ fun getNotificationFooterAlpha(fraction: Float): Float
+
+ /** Returns the alpha for the QS panel. */
+ fun getQsAlpha(fraction: Float): Float
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
new file mode 100644
index 000000000000..fd57f21b2e1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.transition
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.LargeScreenUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when on large screens. */
+@SysUISingleton
+internal class LargeScreenShadeInterpolatorImpl
+@Inject
+internal constructor(
+ configurationController: ConfigurationController,
+ private val context: Context,
+ private val splitShadeInterpolator: SplitShadeInterpolator,
+ private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
+) : LargeScreenShadeInterpolator {
+
+ private var inSplitShade = false
+
+ init {
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ }
+ )
+ updateResources()
+ }
+
+ private fun updateResources() {
+ inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
+ }
+
+ private val impl: LargeScreenShadeInterpolator
+ get() =
+ if (inSplitShade) {
+ splitShadeInterpolator
+ } else {
+ portraitShadeInterpolator
+ }
+
+ override fun getBehindScrimAlpha(fraction: Float) = impl.getBehindScrimAlpha(fraction)
+
+ override fun getNotificationScrimAlpha(fraction: Float) =
+ impl.getNotificationScrimAlpha(fraction)
+
+ override fun getNotificationContentAlpha(fraction: Float) =
+ impl.getNotificationContentAlpha(fraction)
+
+ override fun getNotificationFooterAlpha(fraction: Float) =
+ impl.getNotificationFooterAlpha(fraction)
+
+ override fun getQsAlpha(fraction: Float) = impl.getQsAlpha(fraction)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 218e897794fc..4e1c272ead99 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -23,6 +23,8 @@ import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.PanelState
import com.android.systemui.shade.STATE_OPENING
import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -45,7 +47,8 @@ constructor(
private val scrimController: ScrimController,
@Main private val resources: Resources,
private val statusBarStateController: SysuiStatusBarStateController,
- private val headsUpManager: HeadsUpManager
+ private val headsUpManager: HeadsUpManager,
+ private val featureFlags: FeatureFlags,
) {
private var inSplitShade = false
@@ -106,7 +109,8 @@ constructor(
// in case of HUN we can't always use predefined distances to manage scrim
// transition because dragDownPxAmount can start from value bigger than
// splitShadeScrimTransitionDistance
- !headsUpManager.isTrackingHeadsUp
+ !headsUpManager.isTrackingHeadsUp &&
+ !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
private fun isScreenUnlocked() =
statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
new file mode 100644
index 000000000000..423ba8d4ec88
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.transition
+
+import android.util.MathUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the split shade. */
+internal class SplitShadeInterpolator @Inject internal constructor() :
+ LargeScreenShadeInterpolator {
+
+ override fun getBehindScrimAlpha(fraction: Float): Float {
+ // Start delay: 0%
+ // Duration: 40%
+ // End: 40%
+ return mapFraction(start = 0f, end = 0.4f, fraction)
+ }
+
+ override fun getNotificationScrimAlpha(fraction: Float): Float {
+ // Start delay: 39%
+ // Duration: 27%
+ // End: 66%
+ return mapFraction(start = 0.39f, end = 0.66f, fraction)
+ }
+
+ override fun getNotificationContentAlpha(fraction: Float): Float {
+ return getNotificationScrimAlpha(fraction)
+ }
+
+ override fun getNotificationFooterAlpha(fraction: Float): Float {
+ // Start delay: 57.6%
+ // Duration: 32.1%
+ // End: 89.7%
+ return mapFraction(start = 0.576f, end = 0.897f, fraction)
+ }
+
+ override fun getQsAlpha(fraction: Float): Float {
+ return getNotificationScrimAlpha(fraction)
+ }
+
+ private fun mapFraction(start: Float, end: Float, fraction: Float) =
+ MathUtils.constrainedMap(
+ /* rangeMin= */ 0f,
+ /* rangeMax= */ 1f,
+ /* valueMin= */ start,
+ /* valueMax= */ end,
+ /* value= */ fraction
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8f1e0a1a6b16..3709a139e57d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,7 +39,10 @@ import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.LegacySourceType;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.SourceType;
@@ -216,7 +219,15 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (ambientState.isBouncerInTransit()) {
viewState.setAlpha(aboutToShowBouncerProgress(expansion));
} else {
- viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen() || !flags.isEnabled(
+ Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+ } else {
+ LargeScreenShadeInterpolator interpolator =
+ ambientState.getLargeScreenShadeInterpolator();
+ viewState.setAlpha(interpolator.getNotificationContentAlpha(expansion));
+ }
}
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index b0ad6a1ffbd8..37538a3ca93b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -500,8 +500,10 @@ constructor(
private fun updateTextColorFromRegionSampler() {
smartspaceViews.forEach {
- val textColor = regionSamplers.getValue(it).currentForegroundColor()
- it.setPrimaryTextColor(textColor)
+ val textColor = regionSamplers.get(it)?.currentForegroundColor()
+ if (textColor != null) {
+ it.setPrimaryTextColor(textColor)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index bbabde3f444e..1818dc562bb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -153,7 +153,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
private boolean mUpdateSelfBackgroundOnUpdate = true;
- private boolean mNotificationTranslationFinished = false;
private boolean mIsSnoozed;
private boolean mIsFaded;
private boolean mAnimatePinnedRoundness = false;
@@ -209,11 +208,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
private boolean mUserExpanded;
/**
- * Whether the blocking helper is showing on this notification (even if dismissed)
- */
- private boolean mIsBlockingHelperShowing;
-
- /**
* Has this notification been expanded while it was pinned
*/
private boolean mExpandedWhenPinned;
@@ -1565,18 +1559,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- public void setBlockingHelperShowing(boolean isBlockingHelperShowing) {
- mIsBlockingHelperShowing = isBlockingHelperShowing;
- }
-
- public boolean isBlockingHelperShowing() {
- return mIsBlockingHelperShowing;
- }
-
- public boolean isBlockingHelperShowingAndTranslationFinished() {
- return mIsBlockingHelperShowing && mNotificationTranslationFinished;
- }
-
@Override
public View getShelfTransformationTarget() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -2155,10 +2137,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void setTranslation(float translationX) {
invalidate();
- if (isBlockingHelperShowingAndTranslationFinished()) {
- mGuts.setTranslationX(translationX);
- return;
- } else if (mDismissUsingRowTranslationX) {
+ if (mDismissUsingRowTranslationX) {
setTranslationX(translationX);
} else if (mTranslateableViews != null) {
// Translate the group of views
@@ -2186,10 +2165,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return getTranslationX();
}
- if (isBlockingHelperShowingAndCanTranslate()) {
- return mGuts.getTranslationX();
- }
-
if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
// All of the views in the list should have same translation, just use first one.
return mTranslateableViews.get(0).getTranslationX();
@@ -2198,10 +2173,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return 0;
}
- private boolean isBlockingHelperShowingAndCanTranslate() {
- return areGutsExposed() && mIsBlockingHelperShowing && mNotificationTranslationFinished;
- }
-
public Animator getTranslateViewAnimator(final float leftTarget,
AnimatorUpdateListener listener) {
if (mTranslateAnim != null) {
@@ -2223,9 +2194,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public void onAnimationEnd(Animator anim) {
- if (mIsBlockingHelperShowing) {
- mNotificationTranslationFinished = true;
- }
if (!cancelled && leftTarget == 0) {
if (mMenuRow != null) {
mMenuRow.resetMenu();
@@ -2805,9 +2773,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
int intrinsicBefore = getIntrinsicHeight();
mSensitive = sensitive;
mSensitiveHiddenInGeneral = hideSensitive;
- if (intrinsicBefore != getIntrinsicHeight()) {
- // The animation has a few flaws and is highly visible, so jump cut instead.
- notifyHeightChanged(false /* needsAnimation */);
+ int intrinsicAfter = getIntrinsicHeight();
+ if (intrinsicBefore != intrinsicAfter) {
+ boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
+ notifyHeightChanged(needsAnimation);
}
}
@@ -2864,13 +2833,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
View[] publicViews = new View[]{mPublicLayout};
View[] hiddenChildren = showingPublic ? privateViews : publicViews;
View[] shownChildren = showingPublic ? publicViews : privateViews;
+ // disappear/appear overlap: 10 percent of duration
+ long overlap = duration / 10;
+ // disappear duration: 1/3 of duration + half of overlap
+ long disappearDuration = duration / 3 + overlap / 2;
+ // appear duration: 2/3 of duration + half of overlap
+ long appearDuration = (duration - disappearDuration) + overlap / 2;
for (final View hiddenView : hiddenChildren) {
hiddenView.setVisibility(View.VISIBLE);
hiddenView.animate().cancel();
hiddenView.animate()
.alpha(0f)
.setStartDelay(delay)
- .setDuration(duration)
+ .setDuration(disappearDuration)
.withEndAction(() -> {
hiddenView.setVisibility(View.INVISIBLE);
resetAllContentAlphas();
@@ -2882,8 +2857,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
showView.animate().cancel();
showView.animate()
.alpha(1f)
- .setStartDelay(delay)
- .setDuration(duration);
+ .setStartDelay(delay + duration - appearDuration)
+ .setDuration(appearDuration);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 93f08123ab5a..596bdc09efe4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -238,12 +238,11 @@ public class NotificationGuts extends FrameLayout {
}
public void openControls(
- boolean shouldDoCircularReveal,
int x,
int y,
boolean needsFalsingProtection,
@Nullable Runnable onAnimationEnd) {
- animateOpen(shouldDoCircularReveal, x, y, onAnimationEnd);
+ animateOpen(x, y, onAnimationEnd);
setExposed(true /* exposed */, needsFalsingProtection);
}
@@ -300,7 +299,7 @@ public class NotificationGuts extends FrameLayout {
if (mGutsContent == null
|| !mGutsContent.handleCloseControls(save, force)) {
// We only want to do a circular reveal if we're not showing the blocking helper.
- animateClose(x, y, true /* shouldDoCircularReveal */);
+ animateClose(x, y);
setExposed(false, mNeedsFalsingProtection);
if (mClosedListener != null) {
@@ -309,66 +308,45 @@ public class NotificationGuts extends FrameLayout {
}
}
- /** Animates in the guts view via either a fade or a circular reveal. */
- private void animateOpen(
- boolean shouldDoCircularReveal, int x, int y, @Nullable Runnable onAnimationEnd) {
+ /** Animates in the guts view with a circular reveal. */
+ private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) {
if (isAttachedToWindow()) {
- if (shouldDoCircularReveal) {
- double horz = Math.max(getWidth() - x, x);
- double vert = Math.max(getHeight() - y, y);
- float r = (float) Math.hypot(horz, vert);
- // Make sure we'll be visible after the circular reveal
- setAlpha(1f);
- // Circular reveal originating at (x, y)
- Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- a.addListener(new AnimateOpenListener(onAnimationEnd));
- a.start();
- } else {
- // Fade in content
- this.setAlpha(0f);
- this.animate()
- .alpha(1f)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
- .setInterpolator(Interpolators.ALPHA_IN)
- .setListener(new AnimateOpenListener(onAnimationEnd))
- .start();
- }
+ double horz = Math.max(getWidth() - x, x);
+ double vert = Math.max(getHeight() - y, y);
+ float r = (float) Math.hypot(horz, vert);
+ // Make sure we'll be visible after the circular reveal
+ setAlpha(1f);
+ // Circular reveal originating at (x, y)
+ Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ a.addListener(new AnimateOpenListener(onAnimationEnd));
+ a.start();
+
} else {
Log.w(TAG, "Failed to animate guts open");
}
}
- /** Animates out the guts view via either a fade or a circular reveal. */
+ /** Animates out the guts view with a circular reveal. */
@VisibleForTesting
- void animateClose(int x, int y, boolean shouldDoCircularReveal) {
+ void animateClose(int x, int y) {
if (isAttachedToWindow()) {
- if (shouldDoCircularReveal) {
- // Circular reveal originating at (x, y)
- if (x == -1 || y == -1) {
- x = (getLeft() + getRight()) / 2;
- y = (getTop() + getHeight() / 2);
- }
- double horz = Math.max(getWidth() - x, x);
- double vert = Math.max(getHeight() - y, y);
- float r = (float) Math.hypot(horz, vert);
- Animator a = ViewAnimationUtils.createCircularReveal(this,
- x, y, r, 0);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
- a.start();
- } else {
- // Fade in the blocking helper.
- this.animate()
- .alpha(0f)
- .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .setListener(new AnimateCloseListener(this, /* view */mGutsContent))
- .start();
+ // Circular reveal originating at (x, y)
+ if (x == -1 || y == -1) {
+ x = (getLeft() + getRight()) / 2;
+ y = (getTop() + getHeight() / 2);
}
+ double horz = Math.max(getWidth() - x, x);
+ double vert = Math.max(getHeight() - y, y);
+ float r = (float) Math.hypot(horz, vert);
+ Animator a = ViewAnimationUtils.createCircularReveal(this,
+ x, y, r, 0);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+ a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
+ a.start();
} else {
Log.w(TAG, "Failed to animate guts close");
mGutsContent.onFinishedClosing();
@@ -449,7 +427,7 @@ public class NotificationGuts extends FrameLayout {
return mGutsContent != null && mGutsContent.isLeavebehind();
}
- /** Listener for animations executed in {@link #animateOpen(boolean, int, int, Runnable)}. */
+ /** Listener for animations executed in {@link #animateOpen(int, int, Runnable)}. */
private static class AnimateOpenListener extends AnimatorListenerAdapter {
final Runnable mOnAnimationEnd;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 06d40803052e..46f1bb5ebd6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -629,7 +629,6 @@ public class NotificationGutsManager implements NotifGutsViewManager {
!mAccessibilityManager.isTouchExplorationEnabled());
guts.openControls(
- !row.isBlockingHelperShowing(),
x,
y,
needsFalsingProtection,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6f4d6d944033..77ede0471603 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -29,6 +29,8 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -55,6 +57,8 @@ public class AmbientState implements Dumpable {
private final SectionProvider mSectionProvider;
private final BypassController mBypassController;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
/**
* Used to read bouncer states.
*/
@@ -84,7 +88,7 @@ public class AmbientState implements Dumpable {
private float mExpandingVelocity;
private boolean mPanelTracking;
private boolean mExpansionChanging;
- private boolean mPanelFullWidth;
+ private boolean mIsSmallScreen;
private boolean mPulsing;
private boolean mUnlockHintRunning;
private float mHideAmount;
@@ -252,10 +256,14 @@ public class AmbientState implements Dumpable {
@NonNull DumpManager dumpManager,
@NonNull SectionProvider sectionProvider,
@NonNull BypassController bypassController,
- @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ @NonNull FeatureFlags featureFlags) {
mSectionProvider = sectionProvider;
mBypassController = bypassController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
reload(context);
dumpManager.registerDumpable(this);
}
@@ -574,12 +582,12 @@ public class AmbientState implements Dumpable {
return mPanelTracking;
}
- public boolean isPanelFullWidth() {
- return mPanelFullWidth;
+ public boolean isSmallScreen() {
+ return mIsSmallScreen;
}
- public void setPanelFullWidth(boolean panelFullWidth) {
- mPanelFullWidth = panelFullWidth;
+ public void setSmallScreen(boolean smallScreen) {
+ mIsSmallScreen = smallScreen;
}
public void setUnlockHintRunning(boolean unlockHintRunning) {
@@ -736,6 +744,14 @@ public class AmbientState implements Dumpable {
return mIsClosing;
}
+ public LargeScreenShadeInterpolator getLargeScreenShadeInterpolator() {
+ return mLargeScreenShadeInterpolator;
+ }
+
+ public FeatureFlags getFeatureFlags() {
+ return mFeatureFlags;
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("mTopPadding=" + mTopPadding);
@@ -751,7 +767,7 @@ public class AmbientState implements Dumpable {
pw.println("mDimmed=" + mDimmed);
pw.println("mStatusBarState=" + mStatusBarState);
pw.println("mExpansionChanging=" + mExpansionChanging);
- pw.println("mPanelFullWidth=" + mPanelFullWidth);
+ pw.println("mPanelFullWidth=" + mIsSmallScreen);
pw.println("mPulsing=" + mPulsing);
pw.println("mPulseHeight=" + mPulseHeight);
pw.println("mTrackedHeadsUpRow.key=" + logKey(mTrackedHeadsUpRow));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 977e1bb31049..47d8f48feba5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5223,7 +5223,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setIsFullWidth(boolean isFullWidth) {
- mAmbientState.setPanelFullWidth(isFullWidth);
+ mAmbientState.setSmallScreen(isFullWidth);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 14b0763580e9..02621fe4c103 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -70,7 +70,6 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEv
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -171,7 +170,6 @@ public class NotificationStackScrollLayoutController {
private final CentralSurfaces mCentralSurfaces;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- private final ShadeTransitionController mShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final StackStateLogger mStackStateLogger;
@@ -662,7 +660,6 @@ public class NotificationStackScrollLayoutController {
NotifPipelineFlags notifPipelineFlags,
NotifCollection notifCollection,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
- ShadeTransitionController shadeTransitionController,
UiEventLogger uiEventLogger,
NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
@@ -694,7 +691,6 @@ public class NotificationStackScrollLayoutController {
mMetricsLogger = metricsLogger;
mDumpManager = dumpManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
- mShadeTransitionController = shadeTransitionController;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mResources = resources;
@@ -777,7 +773,6 @@ public class NotificationStackScrollLayoutController {
mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
mLockscreenShadeTransitionController.setStackScroller(this);
- mShadeTransitionController.setNotificationStackScrollLayoutController(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
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 a425792f6523..5516edeac344 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
@@ -29,6 +29,9 @@ import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.LegacySourceType;
@@ -135,7 +138,6 @@ public class StackScrollAlgorithm {
AmbientState ambientState) {
for (ExpandableView view : algorithmState.visibleChildren) {
final ViewState viewState = view.getViewState();
-
final boolean isHunGoingToShade = ambientState.isShadeExpanded()
&& view == ambientState.getTrackedHeadsUpRow();
@@ -148,9 +150,14 @@ public class StackScrollAlgorithm {
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
float expansion = ambientState.getExpansionFraction();
- viewState.setAlpha(ambientState.isBouncerInTransit()
- ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
- : ShadeInterpolation.getContentAlpha(expansion));
+ if (ambientState.isBouncerInTransit()) {
+ viewState.setAlpha(
+ BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion));
+ } else if (view instanceof FooterView) {
+ viewState.setAlpha(interpolateFooterAlpha(ambientState));
+ } else {
+ viewState.setAlpha(interpolateNotificationContentAlpha(ambientState));
+ }
}
// For EmptyShadeView if on keyguard, we need to control the alpha to create
@@ -182,6 +189,28 @@ public class StackScrollAlgorithm {
}
}
+ private float interpolateFooterAlpha(AmbientState ambientState) {
+ float expansion = ambientState.getExpansionFraction();
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen()
+ || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(expansion);
+ }
+ LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+ return interpolator.getNotificationFooterAlpha(expansion);
+ }
+
+ private float interpolateNotificationContentAlpha(AmbientState ambientState) {
+ float expansion = ambientState.getExpansionFraction();
+ FeatureFlags flags = ambientState.getFeatureFlags();
+ if (ambientState.isSmallScreen()
+ || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ return ShadeInterpolation.getContentAlpha(expansion);
+ }
+ LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+ return interpolator.getNotificationContentAlpha(expansion);
+ }
+
/**
* How expanded or collapsed notifications are when pulling down the shade.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index b5d51ce2b9ee..dcd219ad94b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3571,7 +3571,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
boolean goingToSleepWithoutAnimation = isGoingToSleep()
&& !mDozeParameters.shouldControlScreenOff();
boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
- || goingToSleepWithoutAnimation;
+ || goingToSleepWithoutAnimation
+ || mDeviceProvisionedController.isFrpActive();
mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled);
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
@@ -3745,10 +3746,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
boolean launchingAffordanceWithPreview = mLaunchingAffordance;
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
-
if (mAlternateBouncerInteractor.isVisibleState()) {
- if (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
- || mTransitionToFullShadeProgress > 0f) {
+ if ((!isOccluded() || isPanelExpanded())
+ && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED
+ || mTransitionToFullShadeProgress > 0f)) {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE);
} else {
mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8fd967594198..f5d2eee35c93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -54,15 +54,18 @@ import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.keyguard.shared.model.ScrimAlpha;
import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -206,7 +209,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private final ScreenOffAnimationController mScreenOffAnimationController;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final SysuiStatusBarStateController mStatusBarStateController;
private GradientColors mColors;
private boolean mNeedsDrawableColorUpdate;
@@ -245,6 +247,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private boolean mWallpaperVisibilityTimedOut;
private int mScrimsVisibility;
private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
+ private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ private final FeatureFlags mFeatureFlags;
private Consumer<Integer> mScrimVisibleListener;
private boolean mBlankScreen;
private boolean mScreenBlankingCallbackCalled;
@@ -265,12 +269,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private CoroutineDispatcher mMainDispatcher;
private boolean mIsBouncerToGoneTransitionRunning = false;
private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
- private final Consumer<Float> mScrimAlphaConsumer =
- (Float alpha) -> {
+ private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
+ (ScrimAlpha alphas) -> {
+ mInFrontAlpha = alphas.getFrontAlpha();
mScrimInFront.setViewAlpha(mInFrontAlpha);
+
+ mNotificationsAlpha = alphas.getNotificationsAlpha();
mNotificationsScrim.setViewAlpha(mNotificationsAlpha);
- mBehindAlpha = alpha;
- mScrimBehind.setViewAlpha(alpha);
+
+ mBehindAlpha = alphas.getBehindAlpha();
+ mScrimBehind.setViewAlpha(mBehindAlpha);
};
Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
@@ -292,15 +300,17 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
KeyguardTransitionInteractor keyguardTransitionInteractor,
- SysuiStatusBarStateController sysuiStatusBarStateController,
- @Main CoroutineDispatcher mainDispatcher) {
+ @Main CoroutineDispatcher mainDispatcher,
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+ FeatureFlags featureFlags) {
mScrimStateListener = lightBarController::setScrimState;
+ mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+ mFeatureFlags = featureFlags;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mStatusBarStateController = sysuiStatusBarStateController;
mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
mHandler = handler;
mMainExecutor = mainExecutor;
@@ -400,7 +410,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
mPrimaryBouncerToGoneTransition, mMainDispatcher);
- collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha(),
+ collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
mScrimAlphaConsumer, mMainDispatcher);
}
@@ -837,16 +847,21 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
if (!mScreenOffAnimationController.shouldExpandNotifications()
&& !mAnimatingPanelExpansionOnUnlock
&& !occluding) {
- if (mClipsQsScrim) {
+ if (mTransparentScrimBackground) {
+ mBehindAlpha = 0;
+ mNotificationsAlpha = 0;
+ } else if (mClipsQsScrim) {
float behindFraction = getInterpolatedFraction();
behindFraction = (float) Math.pow(behindFraction, 0.8f);
- mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
- mNotificationsAlpha =
- mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
+ mBehindAlpha = 1;
+ mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
} else {
- if (mTransparentScrimBackground) {
- mBehindAlpha = 0;
- mNotificationsAlpha = 0;
+ if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+ mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+ mPanelExpansionFraction * mDefaultScrimAlpha);
+ mNotificationsAlpha =
+ mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+ mPanelExpansionFraction);
} else {
// Behind scrim will finish fading in at 30% expansion.
float behindFraction = MathUtils
@@ -1100,8 +1115,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mBehindAlpha = 1;
}
// Prevent notification scrim flicker when transitioning away from keyguard.
- if (mKeyguardStateController.isKeyguardGoingAway()
- && !mStatusBarStateController.leaveOpenOnKeyguardHide()) {
+ if (mKeyguardStateController.isKeyguardGoingAway()) {
mNotificationsAlpha = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3c32131220d7..b6a3ba80da15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -733,7 +733,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
} else {
showBouncerOrKeyguard(hideBouncerWhenShowing);
}
- hideAlternateBouncer(false);
+ if (hideBouncerWhenShowing) {
+ hideAlternateBouncer(false);
+ }
mKeyguardUpdateManager.sendKeyguardReset();
updateStates();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 078a00d33493..189f2e3098fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -27,6 +27,7 @@ import android.app.PendingIntent;
import android.app.TaskStackBuilder;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -45,6 +46,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.EventLogTags;
@@ -74,6 +76,7 @@ import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wmshell.BubblesManager;
+import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -81,6 +84,7 @@ import javax.inject.Inject;
import dagger.Lazy;
+
/**
* Status bar implementation of {@link NotificationActivityStarter}.
*/
@@ -572,16 +576,29 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
});
// not immersive & a fullscreen alert should be shown
- final PendingIntent fullscreenIntent =
+ final PendingIntent fullScreenIntent =
entry.getSbn().getNotification().fullScreenIntent;
- mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
+ mLogger.logSendingFullScreenIntent(entry, fullScreenIntent);
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
entry.getKey());
mCentralSurfaces.wakeUpForFullScreenIntent();
- fullscreenIntent.send();
+ fullScreenIntent.send();
entry.notifyFullScreenIntentLaunched();
mMetricsLogger.count("note_fullscreen", 1);
+
+ String activityName;
+ List<ResolveInfo> resolveInfos = fullScreenIntent.queryIntentComponents(0);
+ if (resolveInfos.size() > 0 && resolveInfos.get(0) != null
+ && resolveInfos.get(0).activityInfo != null
+ && resolveInfos.get(0).activityInfo.name != null) {
+ activityName = resolveInfos.get(0).activityInfo.name;
+ } else {
+ activityName = "";
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.FULL_SCREEN_INTENT_LAUNCHED,
+ fullScreenIntent.getCreatorUid(),
+ activityName);
} catch (PendingIntent.CanceledException e) {
// ignore
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 159f689de370..4156fc152602 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -51,8 +51,8 @@ constructor(
)
}
- fun logOnLost(network: Network) {
- LoggerHelper.logOnLost(buffer, TAG, network)
+ fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+ LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
}
fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index 85729c12cd4c..19f0242040fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -24,9 +24,11 @@ import android.telephony.TelephonyManager.DATA_HANDOVER_IN_PROGRESS
import android.telephony.TelephonyManager.DATA_SUSPENDED
import android.telephony.TelephonyManager.DATA_UNKNOWN
import android.telephony.TelephonyManager.DataState
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
/** Internal enum representation of the telephony data connection states */
-enum class DataConnectionState {
+enum class DataConnectionState : Diffable<DataConnectionState> {
Connected,
Connecting,
Disconnected,
@@ -34,7 +36,17 @@ enum class DataConnectionState {
Suspended,
HandoverInProgress,
Unknown,
- Invalid,
+ Invalid;
+
+ override fun logDiffs(prevVal: DataConnectionState, row: TableRowLogger) {
+ if (prevVal != this) {
+ row.logChange(COL_CONNECTION_STATE, name)
+ }
+ }
+
+ companion object {
+ private const val COL_CONNECTION_STATE = "connectionState"
+ }
}
fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
deleted file mode 100644
index ed7f60b50bb9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.model
-
-import android.annotation.IntRange
-import android.telephony.CellSignalStrength
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.log.table.Diffable
-import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-
-/**
- * Data class containing all of the relevant information for a particular line of service, known as
- * a Subscription in the telephony world. These models are the result of a single telephony listener
- * which has many callbacks which each modify some particular field on this object.
- *
- * The design goal here is to de-normalize fields from the system into our model fields below. So
- * any new field that needs to be tracked should be copied into this data class rather than
- * threading complex system objects through the pipeline.
- */
-data class MobileConnectionModel(
- /** Fields below are from [ServiceStateListener.onServiceStateChanged] */
- val isEmergencyOnly: Boolean = false,
- val isRoaming: Boolean = false,
- /**
- * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
- * current registered operator name in short alphanumeric format. In some cases this name might
- * be preferred over other methods of calculating the network name
- */
- val operatorAlphaShort: String? = null,
-
- /**
- * TODO (b/263167683): Clarify this field
- *
- * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
- * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
- * connection to be in-service if either the voice registration state is IN_SERVICE or the data
- * registration state is IN_SERVICE and NOT IWLAN.
- */
- val isInService: Boolean = false,
-
- /** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */
- val isGsm: Boolean = false,
- @IntRange(from = 0, to = 4)
- val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
- @IntRange(from = 0, to = 4)
- val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
-
- /** Fields below from [DataConnectionStateListener.onDataConnectionStateChanged] */
- val dataConnectionState: DataConnectionState = Disconnected,
-
- /**
- * Fields below from [DataActivityListener.onDataActivity]. See [TelephonyManager] for the
- * values
- */
- val dataActivityDirection: DataActivityModel =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- ),
-
- /** Fields below from [CarrierNetworkListener.onCarrierNetworkChange] */
- val carrierNetworkChangeActive: Boolean = false,
-
- /** Fields below from [DisplayInfoListener.onDisplayInfoChanged]. */
-
- /**
- * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
- * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
- */
- val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
-) : Diffable<MobileConnectionModel> {
- override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
- if (prevVal.dataConnectionState != dataConnectionState) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
- }
-
- if (prevVal.isEmergencyOnly != isEmergencyOnly) {
- row.logChange(COL_EMERGENCY, isEmergencyOnly)
- }
-
- if (prevVal.isRoaming != isRoaming) {
- row.logChange(COL_ROAMING, isRoaming)
- }
-
- if (prevVal.operatorAlphaShort != operatorAlphaShort) {
- row.logChange(COL_OPERATOR, operatorAlphaShort)
- }
-
- if (prevVal.isInService != isInService) {
- row.logChange(COL_IS_IN_SERVICE, isInService)
- }
-
- if (prevVal.isGsm != isGsm) {
- row.logChange(COL_IS_GSM, isGsm)
- }
-
- if (prevVal.cdmaLevel != cdmaLevel) {
- row.logChange(COL_CDMA_LEVEL, cdmaLevel)
- }
-
- if (prevVal.primaryLevel != primaryLevel) {
- row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- }
-
- if (prevVal.dataActivityDirection.hasActivityIn != dataActivityDirection.hasActivityIn) {
- row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
- }
-
- if (prevVal.dataActivityDirection.hasActivityOut != dataActivityDirection.hasActivityOut) {
- row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
- }
-
- if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
- }
-
- if (prevVal.resolvedNetworkType != resolvedNetworkType) {
- row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
- }
- }
-
- override fun logFull(row: TableRowLogger) {
- row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
- row.logChange(COL_EMERGENCY, isEmergencyOnly)
- row.logChange(COL_ROAMING, isRoaming)
- row.logChange(COL_OPERATOR, operatorAlphaShort)
- row.logChange(COL_IS_IN_SERVICE, isInService)
- row.logChange(COL_IS_GSM, isGsm)
- row.logChange(COL_CDMA_LEVEL, cdmaLevel)
- row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
- row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
- row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
- row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
- row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
- }
-
- @VisibleForTesting
- companion object {
- const val COL_EMERGENCY = "EmergencyOnly"
- const val COL_ROAMING = "Roaming"
- const val COL_OPERATOR = "OperatorName"
- const val COL_IS_IN_SERVICE = "IsInService"
- const val COL_IS_GSM = "IsGsm"
- const val COL_CDMA_LEVEL = "CdmaLevel"
- const val COL_PRIMARY_LEVEL = "PrimaryLevel"
- const val COL_CONNECTION_STATE = "ConnectionState"
- const val COL_ACTIVITY_DIRECTION_IN = "DataActivity.In"
- const val COL_ACTIVITY_DIRECTION_OUT = "DataActivity.Out"
- const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
- const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index 5562e73f0478..cf7a313a4cb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,8 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.model
import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
/**
@@ -26,11 +30,19 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
* on whether or not the display info contains an override type, we may have to call different
* methods on [MobileMappingsProxy] to generate an icon lookup key.
*/
-sealed interface ResolvedNetworkType {
+sealed interface ResolvedNetworkType : Diffable<ResolvedNetworkType> {
val lookupKey: String
+ override fun logDiffs(prevVal: ResolvedNetworkType, row: TableRowLogger) {
+ if (prevVal != this) {
+ row.logChange(COL_NETWORK_TYPE, this.toString())
+ }
+ }
+
object UnknownNetworkType : ResolvedNetworkType {
- override val lookupKey: String = "unknown"
+ override val lookupKey: String = MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN)
+
+ override fun toString(): String = "Unknown"
}
data class DefaultNetworkType(
@@ -47,5 +59,11 @@ sealed interface ResolvedNetworkType {
override val lookupKey: String = "cwf"
val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI
+
+ override fun toString(): String = "CarrierMerged"
+ }
+
+ companion object {
+ private const val COL_NETWORK_TYPE = "networkType"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 6187f64e011d..90c32dc08045 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -17,11 +17,12 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import android.telephony.SubscriptionInfo
-import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.StateFlow
/**
@@ -45,11 +46,57 @@ interface MobileConnectionRepository {
*/
val tableLogBuffer: TableLogBuffer
+ /** True if the [android.telephony.ServiceState] says this connection is emergency calls only */
+ val isEmergencyOnly: StateFlow<Boolean>
+
+ /** True if [android.telephony.ServiceState] says we are roaming */
+ val isRoaming: StateFlow<Boolean>
+
+ /**
+ * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+ * current registered operator name in short alphanumeric format. In some cases this name might
+ * be preferred over other methods of calculating the network name
+ */
+ val operatorAlphaShort: StateFlow<String?>
+
+ /**
+ * TODO (b/263167683): Clarify this field
+ *
+ * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
+ * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
+ * connection to be in-service if either the voice registration state is IN_SERVICE or the data
+ * registration state is IN_SERVICE and NOT IWLAN.
+ */
+ val isInService: StateFlow<Boolean>
+
+ /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
+ val isGsm: StateFlow<Boolean>
+
+ /**
+ * There is still specific logic in the pipeline that calls out CDMA level explicitly. This
+ * field is not completely orthogonal to [primaryLevel], because CDMA could be primary.
+ */
+ // @IntRange(from = 0, to = 4)
+ val cdmaLevel: StateFlow<Int>
+
+ /** [android.telephony.SignalStrength]'s concept of the overall signal level */
+ // @IntRange(from = 0, to = 4)
+ val primaryLevel: StateFlow<Int>
+
+ /** The current data connection state. See [DataConnectionState] */
+ val dataConnectionState: StateFlow<DataConnectionState>
+
+ /** The current data activity direction. See [DataActivityModel] */
+ val dataActivityDirection: StateFlow<DataActivityModel>
+
+ /** True if there is currently a carrier network change in process */
+ val carrierNetworkChangeActive: StateFlow<Boolean>
+
/**
- * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
- * listener + model.
+ * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+ * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
*/
- val connectionInfo: StateFlow<MobileConnectionModel>
+ val resolvedNetworkType: StateFlow<ResolvedNetworkType>
/** The total number of levels. Used with [SignalDrawable]. */
val numberOfLevels: StateFlow<Int>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
new file mode 100644
index 000000000000..809772eec2f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_GSM
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Demo version of [MobileConnectionRepository]. Note that this class shares all of its flows using
+ * [SharingStarted.WhileSubscribed()] to give the same semantics as using a regular
+ * [MutableStateFlow] while still logging all of the inputs in the same manor as the production
+ * repos.
+ */
+class DemoMobileConnectionRepository(
+ override val subId: Int,
+ override val tableLogBuffer: TableLogBuffer,
+ val scope: CoroutineScope,
+) : MobileConnectionRepository {
+ private val _isEmergencyOnly = MutableStateFlow(false)
+ override val isEmergencyOnly =
+ _isEmergencyOnly
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_EMERGENCY,
+ _isEmergencyOnly.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isEmergencyOnly.value)
+
+ private val _isRoaming = MutableStateFlow(false)
+ override val isRoaming =
+ _isRoaming
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_ROAMING,
+ _isRoaming.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isRoaming.value)
+
+ private val _operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val operatorAlphaShort =
+ _operatorAlphaShort
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_OPERATOR,
+ _operatorAlphaShort.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _operatorAlphaShort.value)
+
+ private val _isInService = MutableStateFlow(false)
+ override val isInService =
+ _isInService
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_IN_SERVICE,
+ _isInService.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
+
+ private val _isGsm = MutableStateFlow(false)
+ override val isGsm =
+ _isGsm
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_GSM,
+ _isGsm.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _isGsm.value)
+
+ private val _cdmaLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val cdmaLevel =
+ _cdmaLevel
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CDMA_LEVEL,
+ _cdmaLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _cdmaLevel.value)
+
+ private val _primaryLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+ override val primaryLevel =
+ _primaryLevel
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_PRIMARY_LEVEL,
+ _primaryLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _primaryLevel.value)
+
+ private val _dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+ override val dataConnectionState =
+ _dataConnectionState
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataConnectionState.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _dataConnectionState.value)
+
+ private val _dataActivityDirection =
+ MutableStateFlow(
+ DataActivityModel(
+ hasActivityIn = false,
+ hasActivityOut = false,
+ )
+ )
+ override val dataActivityDirection =
+ _dataActivityDirection
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataActivityDirection.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _dataActivityDirection.value)
+
+ private val _carrierNetworkChangeActive = MutableStateFlow(false)
+ override val carrierNetworkChangeActive =
+ _carrierNetworkChangeActive
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CARRIER_NETWORK_CHANGE,
+ _carrierNetworkChangeActive.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _carrierNetworkChangeActive.value)
+
+ private val _resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+ MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
+ override val resolvedNetworkType =
+ _resolvedNetworkType
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "", _resolvedNetworkType.value)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), _resolvedNetworkType.value)
+
+ override val numberOfLevels = MutableStateFlow(MobileConnectionRepository.DEFAULT_NUM_LEVELS)
+
+ override val dataEnabled = MutableStateFlow(true)
+
+ override val cdmaRoaming = MutableStateFlow(false)
+
+ override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
+
+ /**
+ * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
+ * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
+ * repository.
+ */
+ fun processDemoMobileEvent(
+ event: FakeNetworkEventModel.Mobile,
+ resolvedNetworkType: ResolvedNetworkType,
+ ) {
+ // This is always true here, because we split out disabled states at the data-source level
+ dataEnabled.value = true
+ networkName.value = NetworkNameModel.IntentDerived(event.name)
+
+ cdmaRoaming.value = event.roaming
+ _isRoaming.value = event.roaming
+ // TODO(b/261029387): not yet supported
+ _isEmergencyOnly.value = false
+ _operatorAlphaShort.value = event.name
+ _isInService.value = (event.level ?: 0) > 0
+ // TODO(b/261029387): not yet supported
+ _isGsm.value = false
+ _cdmaLevel.value = event.level ?: 0
+ _primaryLevel.value = event.level ?: 0
+ // TODO(b/261029387): not yet supported
+ _dataConnectionState.value = DataConnectionState.Connected
+ _dataActivityDirection.value =
+ (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+ _carrierNetworkChangeActive.value = event.carrierNetworkChange
+ _resolvedNetworkType.value = resolvedNetworkType
+ }
+
+ fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
+ // This is always true here, because we split out disabled states at the data-source level
+ dataEnabled.value = true
+ networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
+ numberOfLevels.value = event.numberOfLevels
+ cdmaRoaming.value = false
+ _primaryLevel.value = event.level
+ _cdmaLevel.value = event.level
+ _dataActivityDirection.value = event.activity.toMobileDataActivityModel()
+
+ // These fields are always the same for carrier-merged networks
+ _resolvedNetworkType.value = ResolvedNetworkType.CarrierMergedNetworkType
+ _dataConnectionState.value = DataConnectionState.Connected
+ _isRoaming.value = false
+ _isEmergencyOnly.value = false
+ _operatorAlphaShort.value = null
+ _isInService.value = true
+ _isGsm.value = false
+ _carrierNetworkChangeActive.value = false
+ }
+
+ companion object {
+ private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index e92483232186..3cafb7377260 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -18,30 +18,22 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
import android.content.Context
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
import android.util.Log
import com.android.settingslib.SignalIcon
import com.android.settingslib.mobile.MobileMappings
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
-import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import javax.inject.Inject
@@ -183,7 +175,7 @@ constructor(
private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer {
val tableLogBuffer =
logFactory.getOrCreate(
- "DemoMobileConnectionLog [$subId]",
+ "DemoMobileConnectionLog[$subId]",
MOBILE_CONNECTION_BUFFER_SIZE,
)
@@ -191,6 +183,7 @@ constructor(
DemoMobileConnectionRepository(
subId,
tableLogBuffer,
+ scope,
)
return CacheContainer(repo, lastMobileState = null)
}
@@ -237,23 +230,18 @@ constructor(
}
}
- private fun processEnabledMobileState(state: Mobile) {
+ private fun processEnabledMobileState(event: Mobile) {
// get or create the connection repo, and set its values
- val subId = state.subId ?: DEFAULT_SUB_ID
+ val subId = event.subId ?: DEFAULT_SUB_ID
maybeCreateSubscription(subId)
val connection = getRepoForSubId(subId)
- connectionRepoCache[subId]?.lastMobileState = state
+ connectionRepoCache[subId]?.lastMobileState = event
// TODO(b/261029387): until we have a command, use the most recent subId
defaultDataSubId.value = subId
- // This is always true here, because we split out disabled states at the data-source level
- connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.IntentDerived(state.name)
-
- connection.cdmaRoaming.value = state.roaming
- connection.connectionInfo.value = state.toMobileConnectionModel()
+ connection.processDemoMobileEvent(event, event.dataType.toResolvedNetworkType())
}
private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
@@ -272,13 +260,7 @@ constructor(
defaultDataSubId.value = subId
val connection = getRepoForSubId(subId)
- // This is always true here, because we split out disabled states at the data-source level
- connection.dataEnabled.value = true
- connection.networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
- connection.numberOfLevels.value = event.numberOfLevels
- connection.cdmaRoaming.value = false
- connection.connectionInfo.value = event.toMobileConnectionModel()
- Log.e("CCS", "output connection info = ${connection.connectionInfo.value}")
+ connection.processCarrierMergedEvent(event)
}
private fun maybeRemoveSubscription(subId: Int?) {
@@ -332,29 +314,6 @@ constructor(
private fun subIdsString(): String =
_subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
- private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
- return MobileConnectionModel(
- isEmergencyOnly = false, // TODO(b/261029387): not yet supported
- isRoaming = roaming,
- isInService = (level ?: 0) > 0,
- isGsm = false, // TODO(b/261029387): not yet supported
- cdmaLevel = level ?: 0,
- primaryLevel = level ?: 0,
- dataConnectionState =
- DataConnectionState.Connected, // TODO(b/261029387): not yet supported
- dataActivityDirection = (activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel(),
- carrierNetworkChangeActive = carrierNetworkChange,
- resolvedNetworkType = dataType.toResolvedNetworkType()
- )
- }
-
- private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
- return createCarrierMergedConnectionModel(
- this.level,
- activity.toMobileDataActivityModel(),
- )
- }
-
private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
val key = mobileMappingsReverseLookup.value[this] ?: "dis"
return DefaultNetworkType(key)
@@ -364,8 +323,6 @@ constructor(
private const val TAG = "DemoMobileConnectionsRepo"
private const val DEFAULT_SUB_ID = 1
-
- private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
}
}
@@ -374,18 +331,3 @@ class CacheContainer(
/** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */
var lastMobileState: Mobile?,
)
-
-class DemoMobileConnectionRepository(
- override val subId: Int,
- override val tableLogBuffer: TableLogBuffer,
-) : MobileConnectionRepository {
- override val connectionInfo = MutableStateFlow(MobileConnectionModel())
-
- override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
-
- override val dataEnabled = MutableStateFlow(true)
-
- override val cdmaRoaming = MutableStateFlow(false)
-
- override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index 8f6a87b089f2..94d6d0b1db44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -16,18 +16,17 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.TelephonyManager
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import javax.inject.Inject
@@ -94,16 +93,6 @@ class CarrierMergedConnectionRepository(
}
}
- override val connectionInfo: StateFlow<MobileConnectionModel> =
- combine(network, wifiRepository.wifiActivity) { network, activity ->
- if (network == null) {
- MobileConnectionModel()
- } else {
- createCarrierMergedConnectionModel(network.level, activity)
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
-
override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(ROAMING).asStateFlow()
override val networkName: StateFlow<NetworkNameModel> =
@@ -129,34 +118,54 @@ class CarrierMergedConnectionRepository(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
- override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
+ override val primaryLevel =
+ network
+ .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
- companion object {
- /**
- * Creates an instance of [MobileConnectionModel] that represents a carrier merged network
- * with the given [level] and [activity].
- */
- fun createCarrierMergedConnectionModel(
- level: Int,
- activity: DataActivityModel,
- ): MobileConnectionModel {
- return MobileConnectionModel(
- primaryLevel = level,
- cdmaLevel = level,
- dataActivityDirection = activity,
- // Here and below: These values are always the same for every carrier-merged
- // connection.
- resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
- dataConnectionState = DataConnectionState.Connected,
- isRoaming = ROAMING,
- isEmergencyOnly = false,
- operatorAlphaShort = null,
- isInService = true,
- isGsm = false,
- carrierNetworkChangeActive = false,
+ override val cdmaLevel =
+ network
+ .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val dataActivityDirection = wifiRepository.wifiActivity
+
+ override val resolvedNetworkType =
+ network
+ .map {
+ if (it != null) {
+ ResolvedNetworkType.CarrierMergedNetworkType
+ } else {
+ ResolvedNetworkType.UnknownNetworkType
+ }
+ }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ ResolvedNetworkType.UnknownNetworkType
)
- }
+ override val dataConnectionState =
+ network
+ .map {
+ if (it != null) {
+ DataConnectionState.Connected
+ } else {
+ DataConnectionState.Disconnected
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), DataConnectionState.Disconnected)
+
+ override val isRoaming = MutableStateFlow(false).asStateFlow()
+ override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
+ override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
+ override val isInService = MutableStateFlow(true).asStateFlow()
+ override val isGsm = MutableStateFlow(false).asStateFlow()
+ override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
+
+ override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
+
+ companion object {
// Carrier merged is never roaming
private const val ROAMING = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index a39ea0abce5a..b3737ecd1e0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -114,15 +114,147 @@ class FullMobileConnectionRepository(
.flatMapLatest { it.cdmaRoaming }
.stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)
- override val connectionInfo =
+ override val isEmergencyOnly =
activeRepo
- .flatMapLatest { it.connectionInfo }
+ .flatMapLatest { it.isEmergencyOnly }
.logDiffsForTable(
tableLogBuffer,
columnPrefix = "",
- initialValue = activeRepo.value.connectionInfo.value,
+ columnName = COL_EMERGENCY,
+ activeRepo.value.isEmergencyOnly.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.isEmergencyOnly.value
+ )
+
+ override val isRoaming =
+ activeRepo
+ .flatMapLatest { it.isRoaming }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_ROAMING,
+ activeRepo.value.isRoaming.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value)
+
+ override val operatorAlphaShort =
+ activeRepo
+ .flatMapLatest { it.operatorAlphaShort }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_OPERATOR,
+ activeRepo.value.operatorAlphaShort.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.operatorAlphaShort.value
+ )
+
+ override val isInService =
+ activeRepo
+ .flatMapLatest { it.isInService }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_IN_SERVICE,
+ activeRepo.value.isInService.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
+
+ override val isGsm =
+ activeRepo
+ .flatMapLatest { it.isGsm }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_IS_GSM,
+ activeRepo.value.isGsm.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value)
+
+ override val cdmaLevel =
+ activeRepo
+ .flatMapLatest { it.cdmaLevel }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CDMA_LEVEL,
+ activeRepo.value.cdmaLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value)
+
+ override val primaryLevel =
+ activeRepo
+ .flatMapLatest { it.primaryLevel }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_PRIMARY_LEVEL,
+ activeRepo.value.primaryLevel.value
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value)
+
+ override val dataConnectionState =
+ activeRepo
+ .flatMapLatest { it.dataConnectionState }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.dataConnectionState.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.dataConnectionState.value
+ )
+
+ override val dataActivityDirection =
+ activeRepo
+ .flatMapLatest { it.dataActivityDirection }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.dataActivityDirection.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.dataActivityDirection.value
+ )
+
+ override val carrierNetworkChangeActive =
+ activeRepo
+ .flatMapLatest { it.carrierNetworkChangeActive }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ columnName = COL_CARRIER_NETWORK_CHANGE,
+ activeRepo.value.carrierNetworkChangeActive.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.carrierNetworkChangeActive.value
+ )
+
+ override val resolvedNetworkType =
+ activeRepo
+ .flatMapLatest { it.resolvedNetworkType }
+ .logDiffsForTable(
+ tableLogBuffer,
+ columnPrefix = "",
+ activeRepo.value.resolvedNetworkType.value
+ )
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ activeRepo.value.resolvedNetworkType.value
)
- .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
override val dataEnabled =
activeRepo
@@ -187,4 +319,15 @@ class FullMobileConnectionRepository(
fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
}
}
+
+ companion object {
+ const val COL_EMERGENCY = "emergencyOnly"
+ const val COL_ROAMING = "roaming"
+ const val COL_OPERATOR = "operatorName"
+ const val COL_IS_IN_SERVICE = "isInService"
+ const val COL_IS_GSM = "isGsm"
+ const val COL_CDMA_LEVEL = "cdmaLevel"
+ const val COL_PRIMARY_LEVEL = "primaryLevel"
+ const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index e182bc66081a..f1fc3868d690 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
import android.content.Context
import android.content.IntentFilter
import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
import android.telephony.CellSignalStrengthCdma
import android.telephony.ServiceState
import android.telephony.SignalStrength
@@ -37,7 +38,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
@@ -49,6 +50,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierCon
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -62,10 +64,10 @@ import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.scan
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
@@ -165,83 +167,100 @@ class MobileConnectionRepositoryImpl(
}
.shareIn(scope, SharingStarted.WhileSubscribed())
- private fun updateConnectionState(
- prevState: MobileConnectionModel,
- callbackEvent: CallbackEvent,
- ): MobileConnectionModel =
- when (callbackEvent) {
- is CallbackEvent.OnServiceStateChanged -> {
- val serviceState = callbackEvent.serviceState
- prevState.copy(
- isEmergencyOnly = serviceState.isEmergencyOnly,
- isRoaming = serviceState.roaming,
- operatorAlphaShort = serviceState.operatorAlphaShort,
- isInService = Utils.isInService(serviceState),
- )
- }
- is CallbackEvent.OnSignalStrengthChanged -> {
- val signalStrength = callbackEvent.signalStrength
- val cdmaLevel =
- signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
- strengths ->
- if (!strengths.isEmpty()) {
- strengths[0].level
- } else {
- CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- }
- }
+ override val isEmergencyOnly =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.isEmergencyOnly }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
- val primaryLevel = signalStrength.level
+ override val isRoaming =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.roaming }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
- prevState.copy(
- cdmaLevel = cdmaLevel,
- primaryLevel = primaryLevel,
- isGsm = signalStrength.isGsm,
- )
- }
- is CallbackEvent.OnDataConnectionStateChanged -> {
- prevState.copy(dataConnectionState = callbackEvent.dataState.toDataConnectionType())
- }
- is CallbackEvent.OnDataActivity -> {
- prevState.copy(
- dataActivityDirection = callbackEvent.direction.toMobileDataActivityModel()
- )
- }
- is CallbackEvent.OnCarrierNetworkChange -> {
- prevState.copy(carrierNetworkChangeActive = callbackEvent.active)
- }
- is CallbackEvent.OnDisplayInfoChanged -> {
- val telephonyDisplayInfo = callbackEvent.telephonyDisplayInfo
- val networkType =
- if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
- UnknownNetworkType
- } else if (
- telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
- ) {
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(telephonyDisplayInfo.networkType)
- )
+ override val operatorAlphaShort =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { it.serviceState.operatorAlphaShort }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ override val isInService =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+ .map { Utils.isInService(it.serviceState) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val isGsm =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map { it.signalStrength.isGsm }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val cdmaLevel =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map {
+ it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
+ strengths ->
+ if (strengths.isNotEmpty()) {
+ strengths[0].level
} else {
- OverrideNetworkType(
- mobileMappingsProxy.toIconKeyOverride(
- telephonyDisplayInfo.overrideNetworkType
- )
- )
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
}
- prevState.copy(resolvedNetworkType = networkType)
- }
- is CallbackEvent.OnDataEnabledChanged -> {
- // Not part of this object, handled in a separate flow
- prevState
+ }
}
- }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
- override val connectionInfo = run {
- val initial = MobileConnectionModel()
+ override val primaryLevel =
callbackEvents
- .scan(initial, ::updateConnectionState)
- .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
- }
+ .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+ .map { it.signalStrength.level }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+ override val dataConnectionState =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>()
+ .map { it.dataState.toDataConnectionType() }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
+
+ override val dataActivityDirection =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDataActivity>()
+ .map { it.direction.toMobileDataActivityModel() }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+ )
+
+ override val carrierNetworkChangeActive =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnCarrierNetworkChange>()
+ .map { it.active }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+ override val resolvedNetworkType =
+ callbackEvents
+ .filterIsInstance<CallbackEvent.OnDisplayInfoChanged>()
+ .map {
+ if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
+ UnknownNetworkType
+ } else if (
+ it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
+ ) {
+ DefaultNetworkType(
+ mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
+ )
+ } else {
+ OverrideNetworkType(
+ mobileMappingsProxy.toIconKeyOverride(
+ it.telephonyDisplayInfo.overrideNetworkType
+ )
+ )
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
override val numberOfLevels =
systemUiCarrierConfig.shouldInflateSignalStrength
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index f97e41c018f4..b7da3f27c70a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -266,6 +266,7 @@ constructor(
val callback =
object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
override fun onLost(network: Network) {
+ logger.logOnLost(network, isDefaultNetworkCallback = true)
// Send a disconnected model when lost. Maybe should create a sealed
// type or null here?
trySend(MobileConnectivityModel())
@@ -275,6 +276,11 @@ constructor(
network: Network,
caps: NetworkCapabilities
) {
+ logger.logOnCapabilitiesChanged(
+ network,
+ caps,
+ isDefaultNetworkCallback = true,
+ )
trySend(
MobileConnectivityModel(
isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 4caf2b09a3f2..7df6764fda1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -34,6 +34,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -133,11 +134,9 @@ class MobileIconInteractorImpl(
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
) : MobileIconInteractor {
- private val connectionInfo = connectionRepository.connectionInfo
-
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
- override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
+ override val activity = connectionRepository.dataActivityDirection
override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
@@ -155,11 +154,11 @@ class MobileIconInteractorImpl(
override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
override val networkName =
- combine(connectionInfo, connectionRepository.networkName) { connection, networkName ->
- if (
- networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
- ) {
- NetworkNameModel.IntentDerived(connection.operatorAlphaShort)
+ combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
+ operatorAlphaShort,
+ networkName ->
+ if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+ NetworkNameModel.IntentDerived(operatorAlphaShort)
} else {
networkName
}
@@ -173,19 +172,19 @@ class MobileIconInteractorImpl(
/** Observable for the current RAT indicator icon ([MobileIconGroup]) */
override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
combine(
- connectionInfo,
+ connectionRepository.resolvedNetworkType,
defaultMobileIconMapping,
defaultMobileIconGroup,
isDefault,
- ) { info, mapping, defaultGroup, isDefault ->
+ ) { resolvedNetworkType, mapping, defaultGroup, isDefault ->
if (!isDefault) {
return@combine NOT_DEFAULT_DATA
}
- when (info.resolvedNetworkType) {
+ when (resolvedNetworkType) {
is ResolvedNetworkType.CarrierMergedNetworkType ->
- info.resolvedNetworkType.iconGroupOverride
- else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
+ resolvedNetworkType.iconGroupOverride
+ else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
}
}
.distinctUntilChanged()
@@ -200,17 +199,19 @@ class MobileIconInteractorImpl(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
- override val isEmergencyOnly: StateFlow<Boolean> =
- connectionInfo
- .mapLatest { it.isEmergencyOnly }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isEmergencyOnly = connectionRepository.isEmergencyOnly
override val isRoaming: StateFlow<Boolean> =
- combine(connectionInfo, connectionRepository.cdmaRoaming) { connection, cdmaRoaming ->
- if (connection.carrierNetworkChangeActive) {
+ combine(
+ connectionRepository.carrierNetworkChangeActive,
+ connectionRepository.isGsm,
+ connectionRepository.isRoaming,
+ connectionRepository.cdmaRoaming,
+ ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
+ if (carrierNetworkChangeActive) {
false
- } else if (connection.isGsm) {
- connection.isRoaming
+ } else if (isGsm) {
+ isRoaming
} else {
cdmaRoaming
}
@@ -218,12 +219,17 @@ class MobileIconInteractorImpl(
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val level: StateFlow<Int> =
- combine(connectionInfo, alwaysUseCdmaLevel) { connection, alwaysUseCdmaLevel ->
+ combine(
+ connectionRepository.isGsm,
+ connectionRepository.primaryLevel,
+ connectionRepository.cdmaLevel,
+ alwaysUseCdmaLevel,
+ ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
when {
// GSM connections should never use the CDMA level
- connection.isGsm -> connection.primaryLevel
- alwaysUseCdmaLevel -> connection.cdmaLevel
- else -> connection.primaryLevel
+ isGsm -> primaryLevel
+ alwaysUseCdmaLevel -> cdmaLevel
+ else -> primaryLevel
}
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -236,12 +242,9 @@ class MobileIconInteractorImpl(
)
override val isDataConnected: StateFlow<Boolean> =
- connectionInfo
- .mapLatest { connection -> connection.dataConnectionState == Connected }
+ connectionRepository.dataConnectionState
+ .map { it == Connected }
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val isInService =
- connectionRepository.connectionInfo
- .mapLatest { it.isInService }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val isInService = connectionRepository.isInService
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index 6f29e33b5a17..a96e8ff20dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -38,11 +38,24 @@ object LoggerHelper {
int1 = network.getNetId()
str1 = networkCapabilities.toString()
},
- { "onCapabilitiesChanged[default=$bool1]: net=$int1 capabilities=$str1" }
+ { "on${if (bool1) "Default" else ""}CapabilitiesChanged: net=$int1 capabilities=$str1" }
)
}
- fun logOnLost(buffer: LogBuffer, tag: String, network: Network) {
- buffer.log(tag, LogLevel.INFO, { int1 = network.getNetId() }, { "onLost: net=$int1" })
+ fun logOnLost(
+ buffer: LogBuffer,
+ tag: String,
+ network: Network,
+ isDefaultNetworkCallback: Boolean,
+ ) {
+ buffer.log(
+ tag,
+ LogLevel.INFO,
+ {
+ int1 = network.getNetId()
+ bool1 = isDefaultNetworkCallback
+ },
+ { "on${if (bool1) "Default" else ""}Lost: net=$int1" }
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index ee58160a7d3b..b5e7b7a13505 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -128,6 +128,7 @@ constructor(
}
override fun onLost(network: Network) {
+ logger.logOnLost(network, isDefaultNetworkCallback = true)
// The system no longer has a default network, so wifi is definitely not
// default.
trySend(false)
@@ -179,7 +180,7 @@ constructor(
}
override fun onLost(network: Network) {
- logger.logOnLost(network)
+ logger.logOnLost(network, isDefaultNetworkCallback = false)
wifiNetworkChangeEvents.tryEmit(Unit)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index a32e47592355..bb0b166f7aba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -48,8 +48,8 @@ constructor(
)
}
- fun logOnLost(network: Network) {
- LoggerHelper.logOnLost(buffer, TAG, network)
+ fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+ LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
}
fun logIntent(intentName: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
index 3944c8c77f49..0d09fc184892 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java
@@ -21,7 +21,8 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP
/**
* Controller to cache in process the state of the device provisioning.
* <p>
- * This controller keeps track of the values of device provisioning and user setup complete
+ * This controller keeps track of the values of device provisioning, user setup complete, and
+ * whether Factory Reset Protection is active.
*/
public interface DeviceProvisionedController extends CallbackController<DeviceProvisionedListener> {
@@ -49,6 +50,9 @@ public interface DeviceProvisionedController extends CallbackController<DevicePr
*/
boolean isCurrentUserSetup();
+ /** Returns true when Factory Reset Protection is locking the device. */
+ boolean isFrpActive();
+
/**
* Interface to provide calls when the values tracked change
*/
@@ -69,5 +73,10 @@ public interface DeviceProvisionedController extends CallbackController<DevicePr
* Call when some user changes from not provisioned to provisioned
*/
default void onUserSetupChanged() { }
+
+ /**
+ * Called when the state of FRP changes.
+ */
+ default void onFrpActiveChanged() {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index a6b7d9c56900..32c64f457501 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -60,9 +60,11 @@ open class DeviceProvisionedControllerImpl @Inject constructor(
}
private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED)
+ private val frpActiveUri = secureSettings.getUriFor(Settings.Secure.SECURE_FRP_MODE)
private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE)
private val deviceProvisioned = AtomicBoolean(false)
+ private val frpActive = AtomicBoolean(false)
@GuardedBy("lock")
private val userSetupComplete = SparseBooleanArray()
@GuardedBy("lock")
@@ -89,11 +91,15 @@ open class DeviceProvisionedControllerImpl @Inject constructor(
userId: Int
) {
val updateDeviceProvisioned = deviceProvisionedUri in uris
+ val updateFrp = frpActiveUri in uris
val updateUser = if (userSetupUri in uris) userId else NO_USERS
- updateValues(updateDeviceProvisioned, updateUser)
+ updateValues(updateDeviceProvisioned, updateFrp, updateUser)
if (updateDeviceProvisioned) {
onDeviceProvisionedChanged()
}
+ if (updateFrp) {
+ onFrpActiveChanged()
+ }
if (updateUser != NO_USERS) {
onUserSetupChanged()
}
@@ -103,7 +109,7 @@ open class DeviceProvisionedControllerImpl @Inject constructor(
private val userChangedCallback = object : UserTracker.Callback {
@WorkerThread
override fun onUserChanged(newUser: Int, userContext: Context) {
- updateValues(updateDeviceProvisioned = false, updateUser = newUser)
+ updateValues(updateDeviceProvisioned = false, updateFrp = false, updateUser = newUser)
onUserSwitched()
}
@@ -125,19 +131,27 @@ open class DeviceProvisionedControllerImpl @Inject constructor(
updateValues()
userTracker.addCallback(userChangedCallback, backgroundExecutor)
globalSettings.registerContentObserver(deviceProvisionedUri, observer)
+ globalSettings.registerContentObserver(frpActiveUri, observer)
secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL)
}
@WorkerThread
- private fun updateValues(updateDeviceProvisioned: Boolean = true, updateUser: Int = ALL_USERS) {
+ private fun updateValues(
+ updateDeviceProvisioned: Boolean = true,
+ updateFrp: Boolean = true,
+ updateUser: Int = ALL_USERS
+ ) {
if (updateDeviceProvisioned) {
deviceProvisioned
.set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0)
}
+ if (updateFrp) {
+ frpActive.set(globalSettings.getInt(Settings.Secure.SECURE_FRP_MODE, 0) != 0)
+ }
synchronized(lock) {
if (updateUser == ALL_USERS) {
- val N = userSetupComplete.size()
- for (i in 0 until N) {
+ val n = userSetupComplete.size()
+ for (i in 0 until n) {
val user = userSetupComplete.keyAt(i)
val value = secureSettings
.getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0
@@ -172,6 +186,10 @@ open class DeviceProvisionedControllerImpl @Inject constructor(
return deviceProvisioned.get()
}
+ override fun isFrpActive(): Boolean {
+ return frpActive.get()
+ }
+
override fun isUserSetup(user: Int): Boolean {
val index = synchronized(lock) {
userSetupComplete.indexOfKey(user)
@@ -196,7 +214,13 @@ open class DeviceProvisionedControllerImpl @Inject constructor(
override fun onDeviceProvisionedChanged() {
dispatchChange(
- DeviceProvisionedController.DeviceProvisionedListener::onDeviceProvisionedChanged
+ DeviceProvisionedController.DeviceProvisionedListener::onDeviceProvisionedChanged
+ )
+ }
+
+ override fun onFrpActiveChanged() {
+ dispatchChange(
+ DeviceProvisionedController.DeviceProvisionedListener::onFrpActiveChanged
)
}
@@ -221,6 +245,7 @@ open class DeviceProvisionedControllerImpl @Inject constructor(
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("Device provisioned: ${deviceProvisioned.get()}")
+ pw.println("Factory Reset Protection active: ${frpActive.get()}")
synchronized(lock) {
pw.println("User setup complete: $userSetupComplete")
pw.println("Listeners: $listeners")
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 3f895ad0b5b4..02d447976746 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -32,6 +32,8 @@ import android.os.UserManager
import android.provider.Settings
import android.util.Log
import com.android.internal.util.UserIcons
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.R
import com.android.systemui.SystemUISecondaryUserService
import com.android.systemui.animation.Expandable
@@ -90,6 +92,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
telephonyInteractor: TelephonyInteractor,
broadcastDispatcher: BroadcastDispatcher,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val activityManager: ActivityManager,
private val refreshUsersScheduler: RefreshUsersScheduler,
@@ -286,6 +289,12 @@ constructor(
val isSimpleUserSwitcher: Boolean
get() = repository.isSimpleUserSwitcher()
+ val keyguardUpdateMonitorCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onKeyguardGoingAway() {
+ dismissDialog()
+ }
+ }
init {
refreshUsersScheduler.refreshIfNotPaused()
@@ -316,6 +325,7 @@ constructor(
onBroadcastReceived(intent, previousSelectedUser)
}
.launchIn(applicationScope)
+ keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
}
fun addCallback(callback: UserCallback) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index 064c224b0568..c6da55c1e20a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -119,11 +119,11 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
/**
* Default constructor for {@link ObservableServiceConnection}.
- * @param context The context from which the service will be bound with.
+ * @param context The context from which the service will be bound with.
* @param serviceIntent The intent to bind service with.
- * @param executor The executor for connection callbacks to be delivered on
- * @param transformer A {@link ServiceTransformer} for transforming the resulting service
- * into a desired type.
+ * @param executor The executor for connection callbacks to be delivered on
+ * @param transformer A {@link ServiceTransformer} for transforming the resulting service
+ * into a desired type.
*/
@Inject
public ObservableServiceConnection(Context context, Intent serviceIntent,
@@ -143,7 +143,13 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
* @return {@code true} if initiating binding succeed, {@code false} otherwise.
*/
public boolean bind() {
- final boolean bindResult = mContext.bindService(mServiceIntent, mFlags, mExecutor, this);
+ boolean bindResult = false;
+ try {
+ bindResult = mContext.bindService(mServiceIntent, mFlags, mExecutor, this);
+ } catch (SecurityException e) {
+ Log.d(TAG, "Could not bind to service", e);
+ mContext.unbindService(this);
+ }
mBoundCalled = true;
if (DEBUG) {
Log.d(TAG, "bind. bound:" + bindResult);
@@ -197,7 +203,7 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
Log.d(TAG, "removeCallback:" + callback);
}
- mExecutor.execute(()-> mCallbacks.removeIf(el -> el.get() == callback));
+ mExecutor.execute(() -> mCallbacks.removeIf(el-> el.get() == callback));
}
private void onDisconnected(@DisconnectReason int reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b847d67b448e..1cfcf8cbbdcb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1473,6 +1473,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mHandler.removeMessages(H.SHOW);
if (mIsAnimatingDismiss) {
Log.d(TAG, "dismissH: isAnimatingDismiss");
+ Trace.endSection();
return;
}
mIsAnimatingDismiss = true;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index bbc7bc92e819..4dc4c2cbf93c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -519,6 +519,38 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
}
@Test
+ public void testWillRunDismissFromKeyguardIsTrue() {
+ ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+ when(action.willRunAnimationOnKeyguard()).thenReturn(true);
+ mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
+ }
+
+ @Test
+ public void testWillRunDismissFromKeyguardIsFalse() {
+ ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+ when(action.willRunAnimationOnKeyguard()).thenReturn(false);
+ mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+ }
+
+ @Test
+ public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+ mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
+ null /* cancelAction */);
+
+ mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+ assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+ }
+
+ @Test
public void testOnStartingToHide() {
mKeyguardSecurityContainerController.onStartingToHide();
verify(mInputViewController).onStartingToHide();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 531006da8210..565fc57e81b7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -263,9 +263,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
- assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
- getContext().getResources().getDimensionPixelSize(
- R.dimen.bouncer_user_switcher_y_trans));
assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index f01da2dd3a6b..8a5c5b58d058 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -61,7 +61,7 @@ class FontInterpolatorTest : SysuiTestCase() {
val interp = FontInterpolator()
assertSameAxes(startFont, interp.lerp(startFont, endFont, 0f))
assertSameAxes(endFont, interp.lerp(startFont, endFont, 1f))
- assertSameAxes("'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 496, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
}
@Test
@@ -74,7 +74,7 @@ class FontInterpolatorTest : SysuiTestCase() {
.build()
val interp = FontInterpolator()
- assertSameAxes("'wght' 250, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
+ assertSameAxes("'wght' 249, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index 35cd3d261562..10bfc1b292d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -45,6 +45,8 @@ import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
@@ -62,7 +64,6 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -106,6 +107,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
`when`(userTracker.userId).thenReturn(user)
+ `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
`when`(userTracker.userContext).thenReturn(context)
// Return disabled by default
`when`(packageManager.getComponentEnabledSetting(any()))
@@ -564,6 +566,79 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
assertTrue(controller.getCurrentServices().isEmpty())
}
+ @Test
+ fun testForceReloadQueriesPackageManager() {
+ val user = 10
+ `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
+
+ controller.forceReload()
+ verify(packageManager).queryIntentServicesAsUser(
+ argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
+ argThat(FlagsMatcher(
+ PackageManager.GET_META_DATA.toLong() or
+ PackageManager.GET_SERVICES.toLong() or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
+ )),
+ eq(UserHandle.of(user))
+ )
+ }
+
+ @Test
+ fun testForceReloadUpdatesList() {
+ val resolveInfo = ResolveInfo()
+ resolveInfo.serviceInfo = ServiceInfo(componentName)
+
+ `when`(packageManager.queryIntentServicesAsUser(
+ argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
+ argThat(FlagsMatcher(
+ PackageManager.GET_META_DATA.toLong() or
+ PackageManager.GET_SERVICES.toLong() or
+ PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
+ PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
+ )),
+ any<UserHandle>()
+ )).thenReturn(listOf(resolveInfo))
+
+ controller.forceReload()
+
+ val services = controller.getCurrentServices()
+ assertThat(services.size).isEqualTo(1)
+ assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName)
+ }
+
+ @Test
+ fun testForceReloadCallsListeners() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ @Suppress("unchecked_cast")
+ val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
+ ArgumentCaptor.forClass(List::class.java)
+ as ArgumentCaptor<List<ControlsServiceInfo>>
+
+ val resolveInfo = ResolveInfo()
+ resolveInfo.serviceInfo = ServiceInfo(componentName)
+
+ `when`(packageManager.queryIntentServicesAsUser(
+ any(),
+ any<PackageManager.ResolveInfoFlags>(),
+ any<UserHandle>()
+ )).thenReturn(listOf(resolveInfo))
+
+ reset(mockCallback)
+ controller.forceReload()
+
+ verify(mockCallback).onServicesUpdated(capture(captor))
+
+ val services = captor.value
+
+ assertThat(services.size).isEqualTo(1)
+ assertThat(services[0].serviceInfo.componentName).isEqualTo(componentName)
+ }
+
+
+
private fun ServiceInfo(
componentName: ComponentName,
panelActivityComponentName: ComponentName? = null
@@ -600,7 +675,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
private fun setUpQueryResult(infos: List<ActivityInfo>) {
`when`(
packageManager.queryIntentActivitiesAsUser(
- argThat(IntentMatcher(activityName)),
+ argThat(IntentMatcherComponent(activityName)),
argThat(FlagsMatcher(FLAGS)),
eq(UserHandle.of(user))
)
@@ -609,7 +684,7 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
})
}
- private class IntentMatcher(
+ private class IntentMatcherComponent(
private val componentName: ComponentName
) : ArgumentMatcher<Intent> {
override fun matches(argument: Intent?): Boolean {
@@ -617,6 +692,14 @@ class ControlsListingControllerImplTest : SysuiTestCase() {
}
}
+ private class IntentMatcherAction(
+ private val action: String
+ ) : ArgumentMatcher<Intent> {
+ override fun matches(argument: Intent?): Boolean {
+ return argument?.action == action
+ }
+ }
+
private class FlagsMatcher(
private val flags: Long
) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 9d8084d4f2f4..bd7e98e16b91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -42,6 +42,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
@@ -80,9 +82,10 @@ class ControlsStartableTest : SysuiTestCase() {
fun testNoPreferredPackagesNoDefaultSelected_noNewSelection() {
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
}
@@ -92,9 +95,10 @@ class ControlsStartableTest : SysuiTestCase() {
whenever(authorizedPanelsRepository.getPreferredPackages())
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
- `when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
+ setUpControlsListingControls(emptyList())
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
}
@@ -105,9 +109,10 @@ class ControlsStartableTest : SysuiTestCase() {
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
}
@@ -119,9 +124,10 @@ class ControlsStartableTest : SysuiTestCase() {
`when`(controlsController.getPreferredSelection())
.thenReturn(mock<SelectedItem.PanelItem>())
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController, never()).setPreferredSelection(any())
}
@@ -132,9 +138,10 @@ class ControlsStartableTest : SysuiTestCase() {
.thenReturn(setOf(TEST_PACKAGE_PANEL))
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
}
@@ -149,9 +156,10 @@ class ControlsStartableTest : SysuiTestCase() {
ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true),
ControlsServiceInfo(ComponentName("other_package", "cls"), "non panel", false)
)
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController).setPreferredSelection(listings[0].toPanelItem())
}
@@ -166,9 +174,10 @@ class ControlsStartableTest : SysuiTestCase() {
ControlsServiceInfo(ComponentName("other_package", "cls"), "panel", true),
ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)
)
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController).setPreferredSelection(listings[1].toPanelItem())
}
@@ -176,10 +185,11 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testPreferredSelectionIsPanel_bindOnStart() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
`when`(controlsController.getPreferredSelection()).thenReturn(listings[0].toPanelItem())
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController).bindComponentForPanel(TEST_COMPONENT_PANEL)
}
@@ -187,11 +197,12 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testPreferredSelectionPanel_listingNoPanel_notBind() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
`when`(controlsController.getPreferredSelection())
.thenReturn(SelectedItem.PanelItem("panel", TEST_COMPONENT_PANEL))
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController, never()).bindComponentForPanel(any())
}
@@ -199,10 +210,11 @@ class ControlsStartableTest : SysuiTestCase() {
@Test
fun testNotPanelSelection_noBind() {
val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = false))
- `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+ setUpControlsListingControls(listings)
`when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
createStartable(enabled = true).start()
+ fakeExecutor.runAllReady()
verify(controlsController, never()).bindComponentForPanel(any())
}
@@ -221,6 +233,12 @@ class ControlsStartableTest : SysuiTestCase() {
verify(controlsController, never()).setPreferredSelection(any())
}
+ private fun setUpControlsListingControls(listings: List<ControlsServiceInfo>) {
+ doAnswer { doReturn(listings).`when`(controlsListingController).getCurrentServices() }
+ .`when`(controlsListingController)
+ .forceReload()
+ }
+
private fun createStartable(enabled: Boolean): ControlsStartable {
val component: ControlsComponent =
mock() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index 068852de7a43..95c689737417 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -92,9 +92,6 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor;
- @Captor
- private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
-
@Complication.Category
static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
@@ -189,8 +186,6 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
// Dream entry animations finished.
when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
- final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback();
- stateCallback.onStateChanged();
// Add a complication after entry animations are finished.
final HashSet<ComplicationViewModel> complications = new HashSet<>(
@@ -223,9 +218,4 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
mObserverCaptor.capture());
return mObserverCaptor.getValue();
}
-
- private DreamOverlayStateController.Callback captureOverlayStateCallback() {
- verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
- return mCallbackCaptor.getValue();
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
new file mode 100644
index 000000000000..0e14591c5f53
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class ConditionalRestarterTest : SysuiTestCase() {
+ private lateinit var restarter: ConditionalRestarter
+
+ @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+ val restartDelayMs = 0L
+ val dispatcher = StandardTestDispatcher()
+ val testScope = TestScope(dispatcher)
+
+ val conditionA = FakeCondition()
+ val conditionB = FakeCondition()
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ restarter =
+ ConditionalRestarter(
+ systemExitRestarter,
+ setOf(conditionA, conditionB),
+ restartDelayMs,
+ testScope,
+ dispatcher
+ )
+ }
+
+ @Test
+ fun restart_ImmediatelySatisfied() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = true
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForConditionA() =
+ testScope.runTest {
+ conditionA.canRestart = false
+ conditionB.canRestart = true
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionA.canRestart = true
+ conditionA.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForConditionB() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = false
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionB.canRestart = true
+ conditionB.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ @Test
+ fun restart_WaitsForAllConditions() =
+ testScope.runTest {
+ conditionA.canRestart = true
+ conditionB.canRestart = false
+
+ restarter.restartSystemUI("Restart for test")
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ // B becomes true, but A is now false
+ conditionA.canRestart = false
+ conditionB.canRestart = true
+ conditionB.retryFn?.invoke()
+ advanceUntilIdle()
+ // No restart occurs yet.
+ verify(systemExitRestarter, never()).restartSystemUI(any())
+
+ conditionA.canRestart = true
+ conditionA.retryFn?.invoke()
+ advanceUntilIdle()
+ verify(systemExitRestarter).restartSystemUI(any())
+ }
+
+ class FakeCondition : ConditionalRestarter.Condition {
+ var retryFn: (() -> Unit)? = null
+ var canRestart = false
+
+ override fun canRestartNow(retryFn: () -> Unit): Boolean {
+ this.retryFn = retryFn
+
+ return canRestart
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
deleted file mode 100644
index 6060afe495f5..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2021 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.flags
-
-import android.test.suitebuilder.annotation.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-/**
- * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
- */
-@SmallTest
-class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
- private lateinit var restarter: FeatureFlagsReleaseRestarter
-
- @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var batteryController: BatteryController
- @Mock private lateinit var systemExitRestarter: SystemExitRestarter
- private val executor = FakeExecutor(FakeSystemClock())
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- restarter =
- FeatureFlagsReleaseRestarter(
- wakefulnessLifecycle,
- batteryController,
- executor,
- systemExitRestarter
- )
- }
-
- @Test
- fun testRestart_ScheduledWhenReady() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testRestart_RestartsWhenIdle() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter, never()).restartSystemUI("Restart for test")
- executor.advanceClockToLast()
- executor.runAllReady()
- verify(systemExitRestarter).restartSystemUI(any())
- }
-
- @Test
- fun testRestart_NotScheduledWhenAwake() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(0)
- }
-
- @Test
- fun testRestart_NotScheduledWhenNotPluggedIn() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(false)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(0)
- }
-
- @Test
- fun testRestart_NotDoubleSheduled() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
- restarter.restartSystemUI("Restart for test")
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testWakefulnessLifecycle_CanRestart() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
- whenever(batteryController.isPluggedIn).thenReturn(true)
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
-
- val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
- verify(wakefulnessLifecycle).addObserver(captor.capture())
-
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-
- captor.value.onFinishedGoingToSleep()
- assertThat(executor.numPending()).isEqualTo(1)
- }
-
- @Test
- fun testBatteryController_CanRestart() {
- whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- whenever(batteryController.isPluggedIn).thenReturn(false)
- assertThat(executor.numPending()).isEqualTo(0)
- restarter.restartSystemUI("Restart for test")
-
- val captor =
- ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
- verify(batteryController).addCallback(captor.capture())
-
- whenever(batteryController.isPluggedIn).thenReturn(true)
-
- captor.value.onBatteryLevelChanged(0, true, true)
- assertThat(executor.numPending()).isEqualTo(1)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
new file mode 100644
index 000000000000..647b05a77b90
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.BatteryController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class PluggedInConditionTest : SysuiTestCase() {
+ private lateinit var condition: PluggedInCondition
+
+ @Mock private lateinit var batteryController: BatteryController
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ condition = PluggedInCondition(batteryController)
+ }
+
+ @Test
+ fun testCondition_unplugged() {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+
+ assertThat(condition.canRestartNow({})).isFalse()
+ }
+
+ @Test
+ fun testCondition_pluggedIn() {
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ assertThat(condition.canRestartNow({})).isTrue()
+ }
+
+ @Test
+ fun testCondition_invokesRetry() {
+ whenever(batteryController.isPluggedIn).thenReturn(false)
+ var retried = false
+ val retryFn = { retried = true }
+
+ // No restart yet, but we do register a listener now.
+ assertThat(condition.canRestartNow(retryFn)).isFalse()
+ val captor =
+ ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ whenever(batteryController.isPluggedIn).thenReturn(true)
+
+ captor.value.onBatteryLevelChanged(0, true, true)
+ assertThat(retried).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 686782f59355..f7a773ea30ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,12 +20,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -34,37 +33,45 @@ import org.mockito.MockitoAnnotations
* Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
*/
@SmallTest
-class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
- private lateinit var restarter: FeatureFlagsDebugRestarter
+class ScreenIdleConditionTest : SysuiTestCase() {
+ private lateinit var condition: ScreenIdleCondition
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock private lateinit var systemExitRestarter: SystemExitRestarter
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+ condition = ScreenIdleCondition(wakefulnessLifecycle)
}
@Test
- fun testRestart_ImmediateWhenAsleep() {
+ fun testCondition_awake() {
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+
+ assertThat(condition.canRestartNow {}).isFalse()
+ }
+
+ @Test
+ fun testCondition_asleep() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter).restartSystemUI(any())
+
+ assertThat(condition.canRestartNow {}).isTrue()
}
@Test
- fun testRestart_WaitsForSceenOff() {
+ fun testCondition_invokesRetry() {
whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+ var retried = false
+ val retryFn = { retried = true }
- restarter.restartSystemUI("Restart for test")
- verify(systemExitRestarter, never()).restartSystemUI(any())
-
+ // No restart yet, but we do register a listener now.
+ assertThat(condition.canRestartNow(retryFn)).isFalse()
val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor.capture())
- captor.value.onFinishedGoingToSleep()
+ whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
- verify(systemExitRestarter).restartSystemUI(any())
+ captor.value.onFinishedGoingToSleep()
+ assertThat(retried).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 62c9e5ffbb51..5528b94a691c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -281,6 +281,24 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
}
@Test
+ fun `quickAffordance - hidden when quick settings is visible`() =
+ testScope.runTest {
+ repository.setQuickSettingsVisible(true)
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ )
+ )
+
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+ )
+
+ assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+ }
+
+ @Test
fun `quickAffordance - bottom start affordance hidden while dozing`() =
testScope.runTest {
repository.setDozing(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index fe9098fa5c25..5cd24e675a54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -358,6 +358,50 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
}
@Test
+ fun `LOCKSCREEN to DREAMING`() =
+ testScope.runTest {
+ // GIVEN a device that is not dreaming or dozing
+ keyguardRepository.setDreamingWithOverlay(false)
+ keyguardRepository.setWakefulnessModel(startingToWake())
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ runCurrent()
+
+ // GIVEN a prior transition has run to LOCKSCREEN
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ reset(mockTransitionRepository)
+
+ // WHEN the device begins to dream
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceUntilIdle()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to DREAMING should occur
+ assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.to).isEqualTo(KeyguardState.DREAMING)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun `LOCKSCREEN to DOZING`() =
testScope.runTest {
// GIVEN a device with AOD not available
@@ -967,6 +1011,92 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
coroutineContext.cancelChildren()
}
+ @Test
+ fun `OCCLUDED to GONE`() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN keyguard goes away
+ keyguardRepository.setKeyguardShowing(false)
+ // AND occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to GONE should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.GONE)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
+ fun `OCCLUDED to LOCKSCREEN`() =
+ testScope.runTest {
+ // GIVEN a device on lockscreen
+ keyguardRepository.setKeyguardShowing(true)
+ runCurrent()
+
+ // GIVEN a prior transition has run to OCCLUDED
+ runner.startTransition(
+ testScope,
+ TransitionInfo(
+ ownerName = "",
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ animator =
+ ValueAnimator().apply {
+ duration = 10
+ interpolator = Interpolators.LINEAR
+ },
+ )
+ )
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+ reset(mockTransitionRepository)
+
+ // WHEN occlusion ends
+ keyguardRepository.setKeyguardOccluded(false)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+ }
+ // THEN a transition to LOCKSCREEN should occur
+ assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+ assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
private fun startingToWake() =
WakefulnessModel(
WakefulnessState.STARTING_TO_WAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 2a91799741b7..746f66881a88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -21,7 +21,9 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -44,21 +46,86 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
private lateinit var repository: FakeKeyguardTransitionRepository
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardTransitionRepository()
val interactor = KeyguardTransitionInteractor(repository)
- underTest = PrimaryBouncerToGoneTransitionViewModel(interactor, statusBarStateController)
+ underTest =
+ PrimaryBouncerToGoneTransitionViewModel(
+ interactor,
+ statusBarStateController,
+ primaryBouncerInteractor
+ )
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
}
@Test
- fun scrimBehindAlpha_leaveShadeOpen() =
+ fun bouncerAlpha() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
- val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+ val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+
+ assertThat(values.size).isEqualTo(3)
+ values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun bouncerAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+
+ assertThat(values.size).isEqualTo(3)
+ values.forEach { assertThat(it).isEqualTo(0f) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun scrimAlpha_runDimissFromKeyguard() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<ScrimAlpha>()
+
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0.3f))
+ repository.sendTransitionStep(step(0.6f))
+ repository.sendTransitionStep(step(1f))
+
+ assertThat(values.size).isEqualTo(4)
+ values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun scrimBehindAlpha_leaveShadeOpen() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<ScrimAlpha>()
+
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
@@ -68,7 +135,9 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isEqualTo(1f) }
+ values.forEach {
+ assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
+ }
job.cancel()
}
@@ -76,9 +145,9 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
@Test
fun scrimBehindAlpha_doNotLeaveShadeOpen() =
runTest(UnconfinedTestDispatcher()) {
- val values = mutableListOf<Float>()
+ val values = mutableListOf<ScrimAlpha>()
- val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+ val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
@@ -88,8 +157,10 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
repository.sendTransitionStep(step(1f))
assertThat(values.size).isEqualTo(4)
- values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
- assertThat(values[3]).isEqualTo(0f)
+ values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+ values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+ assertThat(values[3].behindAlpha).isEqualTo(0f)
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
index c7f3fa0830cd..fb20bacee64c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableChangeTest.kt
@@ -121,4 +121,92 @@ class TableChangeTest : SysuiTestCase() {
assertThat(underTest.getName()).doesNotContain("original")
assertThat(underTest.getVal()).isEqualTo("8900")
}
+
+ @Test
+ fun updateTo_emptyToString_isString() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+
+ val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
+ new.set("newString")
+ underTest.updateTo(new)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("newPrefix")
+ assertThat(underTest.getName()).contains("newName")
+ assertThat(underTest.getVal()).isEqualTo("newString")
+ }
+
+ @Test
+ fun updateTo_intToEmpty_isEmpty() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set(42)
+
+ val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
+ underTest.updateTo(new)
+
+ assertThat(underTest.hasData()).isFalse()
+ assertThat(underTest.getName()).contains("newPrefix")
+ assertThat(underTest.getName()).contains("newName")
+ assertThat(underTest.getVal()).isEqualTo("null")
+ }
+
+ @Test
+ fun updateTo_stringToBool_isBool() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set("oldString")
+
+ val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
+ new.set(true)
+ underTest.updateTo(new)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("newPrefix")
+ assertThat(underTest.getName()).contains("newName")
+ assertThat(underTest.getVal()).isEqualTo("true")
+ }
+
+ @Test
+ fun updateTo_intToString_isString() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set(43)
+
+ val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
+ new.set("newString")
+ underTest.updateTo(new)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("newPrefix")
+ assertThat(underTest.getName()).contains("newName")
+ assertThat(underTest.getVal()).isEqualTo("newString")
+ }
+
+ @Test
+ fun updateTo_boolToInt_isInt() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set(false)
+
+ val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
+ new.set(44)
+ underTest.updateTo(new)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("newPrefix")
+ assertThat(underTest.getName()).contains("newName")
+ assertThat(underTest.getVal()).isEqualTo("44")
+ }
+
+ @Test
+ fun updateTo_boolToNewBool_isNewBool() {
+ val underTest = TableChange(columnPrefix = "fakePrefix", columnName = "fakeName")
+ underTest.set(false)
+
+ val new = TableChange(columnPrefix = "newPrefix", columnName = "newName")
+ new.set(true)
+ underTest.updateTo(new)
+
+ assertThat(underTest.hasData()).isTrue()
+ assertThat(underTest.getName()).contains("newPrefix")
+ assertThat(underTest.getName()).contains("newName")
+ assertThat(underTest.getVal()).isEqualTo("true")
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
index 2c8d7abd4f4a..949fa1cce0cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt
@@ -435,11 +435,236 @@ class TableLogBufferTest : SysuiTestCase() {
assertThat(dumpedString).doesNotContain("testString[0]")
assertThat(dumpedString).doesNotContain("testString[1]")
- assertThat(dumpedString).doesNotContain("testString[2]")
+ // The buffer should contain [MAX_SIZE + 1] entries since we also save the most recently
+ // evicted value.
+ assertThat(dumpedString).contains("testString[2]")
assertThat(dumpedString).contains("testString[3]")
assertThat(dumpedString).contains("testString[${MAX_SIZE + 2}]")
}
+ @Test
+ fun columnEvicted_lastKnownColumnValueInDump() {
+ systemClock.setCurrentTimeMillis(100L)
+ underTest.logChange(prefix = "", columnName = "willBeEvicted", value = "evictedValue")
+
+ // Exactly fill the buffer so that "willBeEvicted" is evicted
+ for (i in 0 until MAX_SIZE) {
+ systemClock.advanceTime(100L)
+ val dumpString = "fillString[$i]"
+ underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
+ }
+
+ val dumpedString = dumpChanges()
+
+ // Expect that we'll have both the evicted column entry...
+ val evictedColumnLog =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "willBeEvicted" +
+ SEPARATOR +
+ "evictedValue"
+ assertThat(dumpedString).contains(evictedColumnLog)
+
+ // ... *and* all of the fillingColumn entries.
+ val firstFillingColumnLog =
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ "fillingColumn" +
+ SEPARATOR +
+ "fillString[0]"
+ val lastFillingColumnLog =
+ TABLE_LOG_DATE_FORMAT.format(1100L) +
+ SEPARATOR +
+ "fillingColumn" +
+ SEPARATOR +
+ "fillString[9]"
+ assertThat(dumpedString).contains(firstFillingColumnLog)
+ assertThat(dumpedString).contains(lastFillingColumnLog)
+ }
+
+ @Test
+ fun multipleColumnsEvicted_allColumnsInDump() {
+ systemClock.setCurrentTimeMillis(100L)
+ underTest.logChange(prefix = "", columnName = "willBeEvictedString", value = "evictedValue")
+ systemClock.advanceTime(100L)
+ underTest.logChange(prefix = "", columnName = "willBeEvictedInt", value = 45)
+ systemClock.advanceTime(100L)
+ underTest.logChange(prefix = "", columnName = "willBeEvictedBool", value = true)
+
+ // Exactly fill the buffer so that all the above columns will be evicted
+ for (i in 0 until MAX_SIZE) {
+ systemClock.advanceTime(100L)
+ val dumpString = "fillString[$i]"
+ underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
+ }
+
+ val dumpedString = dumpChanges()
+
+ // Expect that we'll have all the evicted column entries...
+ val evictedColumnLogString =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "willBeEvictedString" +
+ SEPARATOR +
+ "evictedValue"
+ val evictedColumnLogInt =
+ TABLE_LOG_DATE_FORMAT.format(200L) + SEPARATOR + "willBeEvictedInt" + SEPARATOR + "45"
+ val evictedColumnLogBool =
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ "willBeEvictedBool" +
+ SEPARATOR +
+ "true"
+ assertThat(dumpedString).contains(evictedColumnLogString)
+ assertThat(dumpedString).contains(evictedColumnLogInt)
+ assertThat(dumpedString).contains(evictedColumnLogBool)
+
+ // ... *and* all of the fillingColumn entries.
+ val firstFillingColumnLog =
+ TABLE_LOG_DATE_FORMAT.format(400) +
+ SEPARATOR +
+ "fillingColumn" +
+ SEPARATOR +
+ "fillString[0]"
+ val lastFillingColumnLog =
+ TABLE_LOG_DATE_FORMAT.format(1300) +
+ SEPARATOR +
+ "fillingColumn" +
+ SEPARATOR +
+ "fillString[9]"
+ assertThat(dumpedString).contains(firstFillingColumnLog)
+ assertThat(dumpedString).contains(lastFillingColumnLog)
+ }
+
+ @Test
+ fun multipleColumnsEvicted_differentPrefixSameName_allColumnsInDump() {
+ systemClock.setCurrentTimeMillis(100L)
+ underTest.logChange(prefix = "prefix1", columnName = "sameName", value = "value1")
+ systemClock.advanceTime(100L)
+ underTest.logChange(prefix = "prefix2", columnName = "sameName", value = "value2")
+ systemClock.advanceTime(100L)
+ underTest.logChange(prefix = "prefix3", columnName = "sameName", value = "value3")
+
+ // Exactly fill the buffer so that all the above columns will be evicted
+ for (i in 0 until MAX_SIZE) {
+ systemClock.advanceTime(100L)
+ val dumpString = "fillString[$i]"
+ underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
+ }
+
+ val dumpedString = dumpChanges()
+
+ // Expect that we'll have all the evicted column entries
+ val evictedColumn1 =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "prefix1.sameName" +
+ SEPARATOR +
+ "value1"
+ val evictedColumn2 =
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ "prefix2.sameName" +
+ SEPARATOR +
+ "value2"
+ val evictedColumn3 =
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ "prefix3.sameName" +
+ SEPARATOR +
+ "value3"
+ assertThat(dumpedString).contains(evictedColumn1)
+ assertThat(dumpedString).contains(evictedColumn2)
+ assertThat(dumpedString).contains(evictedColumn3)
+ }
+
+ @Test
+ fun multipleColumnsEvicted_dumpSortedByTimestamp() {
+ systemClock.setCurrentTimeMillis(100L)
+ underTest.logChange(prefix = "", columnName = "willBeEvictedFirst", value = "evictedValue")
+ systemClock.advanceTime(100L)
+ underTest.logChange(prefix = "", columnName = "willBeEvictedSecond", value = 45)
+ systemClock.advanceTime(100L)
+ underTest.logChange(prefix = "", columnName = "willBeEvictedThird", value = true)
+
+ // Exactly fill the buffer with so that all the above columns will be evicted
+ for (i in 0 until MAX_SIZE) {
+ systemClock.advanceTime(100L)
+ val dumpString = "fillString[$i]"
+ underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
+ }
+
+ val dumpedString = dumpChanges()
+
+ // Expect that we'll have all the evicted column entries in timestamp order
+ val firstEvictedLog =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "willBeEvictedFirst" +
+ SEPARATOR +
+ "evictedValue"
+ val secondEvictedLog =
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ "willBeEvictedSecond" +
+ SEPARATOR +
+ "45"
+ val thirdEvictedLog =
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ "willBeEvictedThird" +
+ SEPARATOR +
+ "true"
+ assertThat(dumpedString).contains(firstEvictedLog)
+ val stringAfterFirst = dumpedString.substringAfter(firstEvictedLog)
+ assertThat(stringAfterFirst).contains(secondEvictedLog)
+ val stringAfterSecond = stringAfterFirst.substringAfter(secondEvictedLog)
+ assertThat(stringAfterSecond).contains(thirdEvictedLog)
+ }
+
+ @Test
+ fun sameColumnEvictedMultipleTimes_onlyLastEvictionInDump() {
+ systemClock.setCurrentTimeMillis(0L)
+
+ for (i in 1 until 4) {
+ systemClock.advanceTime(100L)
+ val dumpString = "evicted[$i]"
+ underTest.logChange(prefix = "", columnName = "evictedColumn", value = dumpString)
+ }
+
+ // Exactly fill the buffer so that all the entries for "evictedColumn" will be evicted.
+ for (i in 0 until MAX_SIZE) {
+ systemClock.advanceTime(100L)
+ val dumpString = "fillString[$i]"
+ underTest.logChange(prefix = "", columnName = "fillingColumn", value = dumpString)
+ }
+
+ val dumpedString = dumpChanges()
+
+ // Expect that we only have the most recent evicted column entry
+ val evictedColumnLog1 =
+ TABLE_LOG_DATE_FORMAT.format(100L) +
+ SEPARATOR +
+ "evictedColumn" +
+ SEPARATOR +
+ "evicted[1]"
+ val evictedColumnLog2 =
+ TABLE_LOG_DATE_FORMAT.format(200L) +
+ SEPARATOR +
+ "evictedColumn" +
+ SEPARATOR +
+ "evicted[2]"
+ val evictedColumnLog3 =
+ TABLE_LOG_DATE_FORMAT.format(300L) +
+ SEPARATOR +
+ "evictedColumn" +
+ SEPARATOR +
+ "evicted[3]"
+ assertThat(dumpedString).doesNotContain(evictedColumnLog1)
+ assertThat(dumpedString).doesNotContain(evictedColumnLog2)
+ assertThat(dumpedString).contains(evictedColumnLog3)
+ }
+
private fun dumpChanges(): String {
underTest.dump(PrintWriter(outputWriter), arrayOf())
return outputWriter.toString()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index ab0669a28f04..d428db7b9dda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -2031,7 +2031,7 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
- fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() {
+ fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
addPlaybackStateAction()
@@ -2051,6 +2051,40 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
+ fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
+ whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+ whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+ addPlaybackStateAction()
+
+ // When a media control using session actions and that does allow resumption is added,
+ addNotificationAndLoad()
+ val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+ mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+ // And then the session is destroyed without timing out first
+ sessionCallbackCaptor.value.invoke(KEY)
+
+ // It is converted to a resume player
+ verify(listener)
+ .onMediaDataLoaded(
+ eq(PACKAGE_NAME),
+ eq(KEY),
+ capture(mediaDataCaptor),
+ eq(true),
+ eq(0),
+ eq(false)
+ )
+ assertThat(mediaDataCaptor.value.resumption).isTrue()
+ assertThat(mediaDataCaptor.value.active).isFalse()
+ verify(logger)
+ .logActiveConvertedToResume(
+ anyInt(),
+ eq(PACKAGE_NAME),
+ eq(mediaDataCaptor.value.instanceId)
+ )
+ }
+
+ @Test
fun testSessionDestroyed_noNotificationKey_stillRemoved() {
whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 4dfa6261b868..9ab728949e40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -313,6 +313,25 @@ class MediaResumeListenerTest : SysuiTestCase() {
}
@Test
+ fun testOnLoadTwice_onlyChecksOnce() {
+ // When data is first loaded,
+ setUpMbsWithValidResolveInfo()
+ resumeListener.onMediaDataLoaded(KEY, null, data)
+
+ // We notify the manager to set a null action
+ verify(mediaDataManager).setResumeAction(KEY, null)
+
+ // If we then get another update from the app before the first check completes
+ assertThat(executor.numPending()).isEqualTo(1)
+ var dataWithCheck = data.copy(hasCheckedForResume = true)
+ resumeListener.onMediaDataLoaded(KEY, null, dataWithCheck)
+
+ // We do not try to start another check
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(mediaDataManager).setResumeAction(KEY, null)
+ }
+
+ @Test
fun testOnUserUnlock_loadsTracks() {
// Set up mock service to successfully find valid media
val description = MediaDescription.Builder().setTitle(TITLE).build()
@@ -392,7 +411,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
}
- verify(sharedPrefsEditor, times(1)).apply()
+ verify(sharedPrefsEditor).apply()
}
@Test
@@ -432,8 +451,8 @@ class MediaResumeListenerTest : SysuiTestCase() {
resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
// We add its resume controls
- verify(resumeBrowser, times(1)).findRecentMedia()
- verify(mediaDataManager, times(1))
+ verify(resumeBrowser).findRecentMedia()
+ verify(mediaDataManager)
.addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
}
@@ -516,7 +535,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
assertThat(result.size).isEqualTo(3)
assertThat(result[2].toLong()).isEqualTo(currentTime)
}
- verify(sharedPrefsEditor, times(1)).apply()
+ verify(sharedPrefsEditor).apply()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 89606bf6be3d..0ab0e2b4b9f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -52,6 +52,8 @@ import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -60,6 +62,7 @@ import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -103,6 +106,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
@Mock private QSSquishinessController mSquishinessController;
@Mock private FooterActionsViewModel mFooterActionsViewModel;
@Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+ @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ @Mock private FeatureFlags mFeatureFlags;
private View mQsFragmentView;
public QSFragmentTest() {
@@ -148,8 +153,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
}
@Test
- public void transitionToFullShade_setsAlphaUsingShadeInterpolator() {
+ public void transitionToFullShade_smallScreen_alphaAlways1() {
QSFragment fragment = resumeAndGetFragment();
+ setIsSmallScreen();
setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
boolean isTransitioningToFullShade = true;
float transitionProgress = 0.5f;
@@ -158,6 +164,43 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
squishinessFraction);
+ assertThat(mQsFragmentView.getAlpha()).isEqualTo(1f);
+ }
+
+ @Test
+ public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true);
+ QSFragment fragment = resumeAndGetFragment();
+ setIsLargeScreen();
+ setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+ boolean isTransitioningToFullShade = true;
+ float transitionProgress = 0.5f;
+ float squishinessFraction = 0.5f;
+ when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+ fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ squishinessFraction);
+
+ assertThat(mQsFragmentView.getAlpha())
+ .isEqualTo(123f);
+ }
+
+ @Test
+ public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false);
+ QSFragment fragment = resumeAndGetFragment();
+ setIsLargeScreen();
+ setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+ boolean isTransitioningToFullShade = true;
+ float transitionProgress = 0.5f;
+ float squishinessFraction = 0.5f;
+ when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+ fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+ squishinessFraction);
+
assertThat(mQsFragmentView.getAlpha())
.isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
}
@@ -514,7 +557,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mock(DumpManager.class),
mock(QSLogger.class),
mock(FooterActionsController.class),
- mFooterActionsViewModelFactory);
+ mFooterActionsViewModelFactory,
+ mLargeScreenShadeInterpolator,
+ mFeatureFlags);
}
private void setUpOther() {
@@ -622,4 +667,12 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
return null;
}).when(view).getLocationOnScreen(any(int[].class));
}
+
+ private void setIsLargeScreen() {
+ getFragment().setIsNotificationPanelFullWidth(false);
+ }
+
+ private void setIsSmallScreen() {
+ getFragment().setIsNotificationPanelFullWidth(true);
+ }
}
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 4f469f753bdf..2dfb6e564d99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -22,8 +22,6 @@ import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -35,6 +33,8 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.annotation.IdRes;
import android.content.ContentResolver;
import android.content.res.Configuration;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
new file mode 100644
index 000000000000..8309342d2c60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
@@ -0,0 +1,144 @@
+package com.android.systemui.shade.transition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.google.common.truth.Expect
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LargeScreenShadeInterpolatorImplTest : SysuiTestCase() {
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val portraitShadeInterpolator = LargeScreenPortraitShadeInterpolator()
+ private val splitShadeInterpolator = SplitShadeInterpolator()
+ private val configurationController = FakeConfigurationController()
+ private val impl =
+ LargeScreenShadeInterpolatorImpl(
+ configurationController,
+ context,
+ splitShadeInterpolator,
+ portraitShadeInterpolator
+ )
+
+ @Test
+ fun getBehindScrimAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getBehindScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getBehindScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getBehindScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationScrimAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getNotificationScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationContentAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationContentAlpha(fraction) }
+ )
+ }
+
+ @Test
+ fun getNotificationContentAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+ expected = { fraction ->
+ portraitShadeInterpolator.getNotificationContentAlpha(fraction)
+ }
+ )
+ }
+
+ @Test
+ fun getNotificationFooterAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getNotificationFooterAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getNotificationFooterAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+ expected = { fraction ->
+ portraitShadeInterpolator.getNotificationFooterAlpha(fraction)
+ }
+ )
+ }
+
+ @Test
+ fun getQsAlpha_inSplitShade_usesSplitShadeValue() {
+ setSplitShadeEnabled(true)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getQsAlpha(fraction) },
+ expected = { fraction -> splitShadeInterpolator.getQsAlpha(fraction) }
+ )
+ }
+ @Test
+ fun getQsAlpha_inPortraitShade_usesPortraitShadeValue() {
+ setSplitShadeEnabled(false)
+
+ assertInterpolation(
+ actual = { fraction -> impl.getQsAlpha(fraction) },
+ expected = { fraction -> portraitShadeInterpolator.getQsAlpha(fraction) }
+ )
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ configurationController.notifyConfigurationChanged()
+ }
+
+ private fun assertInterpolation(
+ actual: (fraction: Float) -> Float,
+ expected: (fraction: Float) -> Float
+ ) {
+ for (i in 0..10) {
+ val fraction = i / 10f
+ expect.that(actual(fraction)).isEqualTo(expected(fraction))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
new file mode 100644
index 000000000000..d24bcdc834a7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.transition
+
+class LinearLargeScreenShadeInterpolator : LargeScreenShadeInterpolator {
+ override fun getBehindScrimAlpha(fraction: Float) = fraction
+ override fun getNotificationScrimAlpha(fraction: Float) = fraction
+ override fun getNotificationContentAlpha(fraction: Float) = fraction
+ override fun getNotificationFooterAlpha(fraction: Float) = fraction
+ override fun getQsAlpha(fraction: Float) = fraction
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index 84f86561d073..cbf54854759b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -5,6 +5,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.shade.STATE_CLOSED
import com.android.systemui.shade.STATE_OPEN
import com.android.systemui.shade.STATE_OPENING
@@ -30,6 +32,7 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var headsUpManager: HeadsUpManager
+ @Mock private lateinit var featureFlags: FeatureFlags
private val configurationController = FakeConfigurationController()
private lateinit var controller: ScrimShadeTransitionController
@@ -45,7 +48,8 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
scrimController,
context.resources,
statusBarStateController,
- headsUpManager)
+ headsUpManager,
+ featureFlags)
controller.onPanelStateChanged(STATE_OPENING)
}
@@ -107,6 +111,19 @@ class ScrimShadeTransitionControllerTest : SysuiTestCase() {
}
@Test
+ fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true)
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.SHADE)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
whenever(statusBarStateController.currentOrUpcomingState)
.thenReturn(StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index cc45cf88fa18..2eca78a0412b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -57,7 +57,18 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.measure(50, 50)
verify(mockTextAnimator).glyphFilter = any()
- verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 300,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = false,
+ duration = 350L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
verifyNoMoreInteractions(mockTextAnimator)
}
@@ -68,8 +79,30 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.animateAppearOnLockscreen()
verify(mockTextAnimator, times(2)).glyphFilter = any()
- verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
- verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 100,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = false,
+ duration = 0L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
+ verify(mockTextAnimator)
+ .setTextStyle(
+ weight = 300,
+ textSize = -1.0f,
+ color = 200,
+ strokeWidth = -1F,
+ animate = true,
+ duration = 350L,
+ interpolator = null,
+ delay = 0L,
+ onAnimationEnd = null
+ )
verifyNoMoreInteractions(mockTextAnimator)
}
}
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 9e23d548e5b5..957b0f10ec1f 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
@@ -103,6 +103,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+ fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
}
@@ -402,17 +403,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception {
- ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
-
- group.setBlockingHelperShowing(true);
- assertTrue(group.isBlockingHelperShowing());
-
- group.setBlockingHelperShowing(false);
- assertFalse(group.isBlockingHelperShowing());
- }
-
- @Test
public void testGetNumUniqueChildren_defaultChannel() throws Exception {
ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index d7ac6b41ee78..3d8a74466a5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -117,7 +117,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationActivityStarter mNotificationActivityStarter;
@Mock private NotificationListContainer mNotificationListContainer;
- @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
@Mock private OnSettingsClickListener mOnSettingsClickListener;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private CentralSurfaces mCentralSurfaces;
@@ -173,7 +172,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
// Test doesn't support animation since the guts view is not attached.
doNothing().when(guts).openControls(
- eq(true) /* shouldDoCircularReveal */,
anyInt(),
anyInt(),
anyBoolean(),
@@ -190,7 +188,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
assertEquals(View.INVISIBLE, guts.getVisibility());
mTestableLooper.processAllMessages();
verify(guts).openControls(
- eq(true),
anyInt(),
anyInt(),
anyBoolean(),
@@ -213,7 +210,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
// Test doesn't support animation since the guts view is not attached.
doNothing().when(guts).openControls(
- eq(true) /* shouldDoCircularReveal */,
anyInt(),
anyInt(),
anyBoolean(),
@@ -237,7 +233,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
mTestableLooper.processAllMessages();
verify(guts).openControls(
- eq(true),
anyInt(),
anyInt(),
anyBoolean(),
@@ -379,7 +374,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
public void testInitializeNotificationInfoView_PassesAlongProvisionedState() throws Exception {
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
- row.setBlockingHelperShowing(false);
modifyRanking(row.getEntry())
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
.build();
@@ -414,7 +408,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
NotificationInfo notificationInfoView = mock(NotificationInfo.class);
ExpandableNotificationRow row = spy(mHelper.createRow());
- row.setBlockingHelperShowing(true);
modifyRanking(row.getEntry())
.setUserSentiment(USER_SENTIMENT_NEGATIVE)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
index e696c8738d72..fdfb4f4612fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -76,7 +76,7 @@ class NotificationGutsTest : SysuiTestCase() {
fun openControls() {
guts.gutsContent = gutsContent
- guts.openControls(true, 0, 0, false, null)
+ guts.openControls(0, 0, false, null)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 87f4c323b7cc..09382ec1945e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -20,6 +20,8 @@ import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
@@ -39,6 +41,8 @@ class AmbientStateTest : SysuiTestCase() {
private val sectionProvider = StackScrollAlgorithm.SectionProvider { _, _ -> false }
private val bypassController = StackScrollAlgorithm.BypassController { false }
private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+ private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+ private val featureFlags = mock<FeatureFlags>()
private lateinit var sut: AmbientState
@@ -51,6 +55,8 @@ class AmbientStateTest : SysuiTestCase() {
sectionProvider,
bypassController,
statusBarKeyguardViewManager,
+ largeScreenShadeInterpolator,
+ featureFlags
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 9d759c4b6016..b1d3daa27eae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -7,6 +7,9 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.LegacySourceType
@@ -21,7 +24,9 @@ import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
/**
@@ -32,6 +37,9 @@ import org.mockito.Mockito.`when` as whenever
@RunWithLooper
class NotificationShelfTest : SysuiTestCase() {
+ @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
+ @Mock private lateinit var flags: FeatureFlags
+
private val shelf = NotificationShelf(
context,
/* attrs */ null,
@@ -50,8 +58,12 @@ class NotificationShelfTest : SysuiTestCase() {
@Before
fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
+ whenever(ambientState.featureFlags).thenReturn(flags)
shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController)
shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+ whenever(ambientState.isSmallScreen).thenReturn(true)
}
@Test
@@ -295,7 +307,35 @@ class NotificationShelfTest : SysuiTestCase() {
fun updateState_expansionChanging_shelfAlphaUpdated() {
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.6f,
- expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f)
+ expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f),
+ )
+ }
+
+ @Test
+ fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+ val expansionFraction = 0.6f
+ whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
+ whenever(ambientState.isSmallScreen).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(0.123f)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = 0.123f
+ )
+ }
+
+ @Test
+ fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
+ val expansionFraction = 0.6f
+ whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
+ whenever(ambientState.isSmallScreen).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(0.123f)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
)
}
@@ -305,7 +345,17 @@ class NotificationShelfTest : SysuiTestCase() {
updateState_expansionChanging_shelfAlphaUpdated(
expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f)
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
+ )
+ }
+
+ @Test
+ fun updateState_largeScreen_expansionChangingWhileBouncerInTransit_bouncerInterpolatorUsed() {
+ whenever(ambientState.isBouncerInTransit).thenReturn(true)
+
+ updateState_expansionChanging_shelfAlphaUpdated(
+ expansionFraction = 0.95f,
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index ff26a43c0006..45ae96c10345 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -52,7 +52,6 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -135,7 +134,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private StackStateLogger mStackLogger;
@Mock private NotificationStackScrollLogger mLogger;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
- @Mock private ShadeTransitionController mShadeTransitionController;
@Mock private FeatureFlags mFeatureFlags;
@Mock private NotificationTargetsHelper mNotificationTargetsHelper;
@Mock private SecureSettings mSecureSettings;
@@ -183,7 +181,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mNotifPipelineFlags,
mNotifCollection,
mLockscreenShadeTransitionController,
- mShadeTransitionController,
mUiEventLogger,
mRemoteInputManager,
mVisibilityLocationProviderDelegator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index cbf841b5a1f7..7153e59fff37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -68,7 +68,9 @@ import com.android.systemui.ExpandHelper;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
@@ -129,6 +131,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Mock private NotificationShelf mNotificationShelf;
@Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+ @Mock private FeatureFlags mFeatureFlags;
@Before
@UiThreadTest
@@ -142,7 +146,10 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mDumpManager,
mNotificationSectionsManager,
mBypassController,
- mStatusBarKeyguardViewManager));
+ mStatusBarKeyguardViewManager,
+ mLargeScreenShadeInterpolator,
+ mFeatureFlags
+ ));
// Inject dependencies before initializing the layout
mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4d9db8c28e07..7f20f1e53d97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -8,6 +8,9 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.EmptyShadeView
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.StatusBarState
@@ -15,11 +18,13 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
@@ -30,12 +35,19 @@ import org.mockito.Mockito.`when` as whenever
@SmallTest
class StackScrollAlgorithmTest : SysuiTestCase() {
+
+ @JvmField @Rule
+ var expect: Expect = Expect.create()
+
+ private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+
private val hostView = FrameLayout(context)
private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
private val notificationRow = mock<ExpandableNotificationRow>()
private val dumpManager = mock<DumpManager>()
private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
private val notificationShelf = mock<NotificationShelf>()
+ private val featureFlags = mock<FeatureFlags>()
private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
}
@@ -44,8 +56,10 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
dumpManager,
/* sectionProvider */ { _, _ -> false },
/* bypassController */ { false },
- mStatusBarKeyguardViewManager
- )
+ mStatusBarKeyguardViewManager,
+ largeScreenShadeInterpolator,
+ featureFlags,
+ )
private val testableResources = mContext.getOrCreateTestableResources()
@@ -59,6 +73,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
fun setUp() {
whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+ ambientState.isSmallScreen = true
hostView.addView(notificationRow)
}
@@ -145,11 +160,46 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
}
@Test
- fun resetViewStates_expansionChangingWhileBouncerInTransit_notificationAlphaUpdated() {
+ fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+ val expansionFraction = 0.6f
+ val surfaceAlpha = 123f
+ ambientState.isSmallScreen = false
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(surfaceAlpha)
+
+ resetViewStates_expansionChanging_notificationAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = surfaceAlpha,
+ )
+ }
+
+ @Test
+ fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
+ val expansionFraction = 0.6f
+ val surfaceAlpha = 123f
+ ambientState.isSmallScreen = false
+ whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false)
+ whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+ whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+ .thenReturn(surfaceAlpha)
+
+ resetViewStates_expansionChanging_notificationAlphaUpdated(
+ expansionFraction = expansionFraction,
+ expectedAlpha = getContentAlpha(expansionFraction),
+ )
+ }
+
+ @Test
+ fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
+ ambientState.isSmallScreen = false
whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction = 0.95f,
- expectedAlpha = aboutToShowBouncerProgress(0.95f)
+ expectedAlpha = aboutToShowBouncerProgress(0.95f),
)
}
@@ -696,7 +746,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
expansionFraction: Float,
- expectedAlpha: Float
+ expectedAlpha: Float,
) {
ambientState.isExpansionChanging = true
ambientState.expansionFraction = expansionFraction
@@ -704,7 +754,7 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
- assertThat(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
+ expect.that(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5a5b1424758f..98f5a10aa203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -1036,6 +1036,34 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
}
@Test
+ public void testOccludingQSNotExpanded_transitionToAuthScrimmed() {
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN device occluded and panel is NOT expanded
+ mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mCentralSurfaces.mPanelExpanded = false;
+
+ mCentralSurfaces.updateScrimController();
+
+ verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED));
+ }
+
+ @Test
+ public void testOccludingQSExpanded_transitionToAuthScrimmedShade() {
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
+
+ // GIVEN device occluded and qs IS expanded
+ mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mCentralSurfaces.mPanelExpanded = true;
+
+ mCentralSurfaces.updateScrimController();
+
+ verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE));
+ }
+
+ @Test
public void testShowKeyguardImplementation_setsState() {
when(mLockscreenUserManager.getCurrentProfiles()).thenReturn(new SparseArray<>());
@@ -1259,6 +1287,15 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
verify(mPowerManagerService, never()).wakeUp(anyLong(), anyInt(), anyString(), anyString());
}
+ @Test
+ public void frpLockedDevice_shadeDisabled() {
+ when(mDeviceProvisionedController.isFrpActive()).thenReturn(true);
+ when(mDozeServiceHost.isPulsing()).thenReturn(true);
+ mCentralSurfaces.updateNotificationPanelTouchState();
+
+ verify(mNotificationPanelViewController).setTouchAndAnimationDisabled(true);
+ }
+
/**
* Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
* to reconfigure the keyguard to reflect the requested showing/occluded states.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index e1fba816382c..7a1270f3521d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -24,8 +24,6 @@ import static com.android.systemui.statusbar.phone.ScrimState.SHADE_LOCKED;
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -41,6 +39,8 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
import android.animation.Animator;
import android.app.AlarmManager;
import android.graphics.Color;
@@ -59,6 +59,8 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
@@ -67,7 +69,8 @@ import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.scrim.ScrimView;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LinearLargeScreenShadeInterpolator;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -104,6 +107,8 @@ public class ScrimControllerTest extends SysuiTestCase {
private final FakeConfigurationController mConfigurationController =
new FakeConfigurationController();
+ private final LargeScreenShadeInterpolator
+ mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
private ScrimController mScrimController;
private ScrimView mScrimBehind;
@@ -128,11 +133,11 @@ public class ScrimControllerTest extends SysuiTestCase {
@Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@Mock private CoroutineDispatcher mMainDispatcher;
- @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ @Mock private FeatureFlags mFeatureFlags;
private static class AnimatorListener implements Animator.AnimatorListener {
private int mNumStarts;
@@ -242,20 +247,28 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
.thenReturn(emptyFlow());
- when(mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha())
+ when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
.thenReturn(emptyFlow());
- mScrimController = new ScrimController(mLightBarController,
- mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
- new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
- mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+ mScrimController = new ScrimController(
+ mLightBarController,
+ mDozeParameters,
+ mAlarmManager,
+ mKeyguardStateController,
+ mDelayedWakeLockBuilder,
+ new FakeHandler(mLooper.getLooper()),
+ mKeyguardUpdateMonitor,
+ mDockManager,
+ mConfigurationController,
+ new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
- mSysuiStatusBarStateController,
- mMainDispatcher);
+ mMainDispatcher,
+ mLinearLargeScreenShadeInterpolator,
+ mFeatureFlags);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -653,7 +666,81 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- public void transitionToUnlocked() {
+ public void transitionToUnlocked_clippedQs() {
+ mScrimController.setClipsQsScrim(true);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mNotificationsScrim, false,
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, OPAQUE));
+
+ mScrimController.setRawPanelExpansionFraction(0.25f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, OPAQUE));
+
+ mScrimController.setRawPanelExpansionFraction(0.5f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, OPAQUE,
+ mScrimBehind, OPAQUE));
+ }
+
+ @Test
+ public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(true);
+ mScrimController.setClipsQsScrim(false);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ finishAnimationsImmediately();
+
+ assertScrimTinted(Map.of(
+ mNotificationsScrim, false,
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ // The large screens interpolator used in this test is a linear one, just for tests.
+ // Assertions below are based on this assumption, and that the code uses that interpolator
+ // when on a large screen (QS not clipped).
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(0.5f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(0.99f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, SEMI_TRANSPARENT,
+ mScrimBehind, SEMI_TRANSPARENT));
+
+ mScrimController.setRawPanelExpansionFraction(1f);
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mNotificationsScrim, OPAQUE,
+ mScrimBehind, OPAQUE));
+ }
+
+
+ @Test
+ public void transitionToUnlocked_nonClippedQs_flagFalse() {
+ when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+ .thenReturn(false);
mScrimController.setClipsQsScrim(false);
mScrimController.setRawPanelExpansionFraction(0f);
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -691,7 +778,6 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimBehind, OPAQUE));
}
-
@Test
public void scrimStateCallback() {
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -879,17 +965,25 @@ public class ScrimControllerTest extends SysuiTestCase {
// GIVEN display does NOT need blanking
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
- mScrimController = new ScrimController(mLightBarController,
- mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
- new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
- mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+ mScrimController = new ScrimController(
+ mLightBarController,
+ mDozeParameters,
+ mAlarmManager,
+ mKeyguardStateController,
+ mDelayedWakeLockBuilder,
+ new FakeHandler(mLooper.getLooper()),
+ mKeyguardUpdateMonitor,
+ mDockManager,
+ mConfigurationController,
+ new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager,
mPrimaryBouncerToGoneTransitionViewModel,
mKeyguardTransitionInteractor,
- mSysuiStatusBarStateController,
- mMainDispatcher);
+ mMainDispatcher,
+ mLinearLargeScreenShadeInterpolator,
+ mFeatureFlags);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 158e9adcff43..e2019b2814d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -683,4 +683,30 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
// the following call before registering centralSurfaces should NOT throw a NPE:
mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
}
+
+ @Test
+ public void testResetHideBouncerWhenShowing_alternateBouncerHides() {
+ // GIVEN the keyguard is showing
+ reset(mAlternateBouncerInteractor);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ // WHEN SBKV is reset with hideBouncerWhenShowing=true
+ mStatusBarKeyguardViewManager.reset(true);
+
+ // THEN alternate bouncer is hidden
+ verify(mAlternateBouncerInteractor).hide();
+ }
+
+ @Test
+ public void testResetHideBouncerWhenShowingIsFalse_alternateBouncerHides() {
+ // GIVEN the keyguard is showing
+ reset(mAlternateBouncerInteractor);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ // WHEN SBKV is reset with hideBouncerWhenShowing=false
+ mStatusBarKeyguardViewManager.reset(false);
+
+ // THEN alternate bouncer is NOT hidden
+ verify(mAlternateBouncerInteractor, never()).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ccc57ad72f36..ee7e082c839f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -18,6 +18,9 @@ package com.android.systemui.statusbar.phone;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
import static org.mockito.AdditionalAnswers.answerVoid;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -28,7 +31,6 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -38,6 +40,8 @@ import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -51,6 +55,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.SysuiTestCase;
@@ -90,9 +95,12 @@ import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -398,4 +406,40 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
// THEN display should try wake up for the full screen intent
verify(mCentralSurfaces).wakeUpForFullScreenIntent();
}
+
+ @Test
+ public void testOnFullScreenIntentWhenDozing_logToStatsd() {
+ final int kTestUid = 12345;
+ final String kTestActivityName = "TestActivity";
+ // GIVEN entry that can has a full screen intent that can show
+ PendingIntent mockFullScreenIntent = mock(PendingIntent.class);
+ when(mockFullScreenIntent.getCreatorUid()).thenReturn(kTestUid);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.name = kTestActivityName;
+ when(mockFullScreenIntent.queryIntentComponents(anyInt()))
+ .thenReturn(Arrays.asList(resolveInfo));
+ Notification.Builder nb = new Notification.Builder(mContext, "a")
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setFullScreenIntent(mockFullScreenIntent, true);
+ StatusBarNotification sbn = new StatusBarNotification("pkg", "pkg", 0,
+ "tag" + System.currentTimeMillis(), 0, 0,
+ nb.build(), new UserHandle(0), null, 0);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
+ when(entry.getSbn()).thenReturn(sbn);
+ MockitoSession mockingSession = mockitoSession()
+ .mockStatic(FrameworkStatsLog.class)
+ .strictness(Strictness.LENIENT)
+ .startMocking();
+
+ // WHEN
+ mNotificationActivityStarter.launchFullScreenIntent(entry);
+
+ // THEN the full screen intent should be logged to statsd.
+ verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.FULL_SCREEN_INTENT_LAUNCHED,
+ kTestUid, kTestActivityName));
+ mockingSession.finishMocking();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
index 35dea60b1a1f..7c9351c8495d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
@@ -47,14 +47,14 @@ class MobileInputLoggerTest : SysuiTestCase() {
val expectedNetId = NET_1_ID.toString()
val expectedCaps = NET_1_CAPS.toString()
- assertThat(actualString).contains("true")
+ assertThat(actualString).contains("onDefaultCapabilitiesChanged")
assertThat(actualString).contains(expectedNetId)
assertThat(actualString).contains(expectedCaps)
}
@Test
fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
- logger.logOnLost(NET_1)
+ logger.logOnLost(NET_1, isDefaultNetworkCallback = false)
val stringWriter = StringWriter()
buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -62,6 +62,7 @@ class MobileInputLoggerTest : SysuiTestCase() {
val expectedNetId = NET_1_ID.toString()
+ assertThat(actualString).contains("onLost")
assertThat(actualString).contains(expectedNetId)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
deleted file mode 100644
index 45189cf8d432..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.pipeline.mobile.data.model
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_IN
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_OUT
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-@SmallTest
-class MobileConnectionModelTest : SysuiTestCase() {
-
- @Test
- fun `log diff - initial log contains all columns`() {
- val logger = TestLogger()
- val connection = MobileConnectionModel()
-
- connection.logFull(logger)
-
- assertThat(logger.changes)
- .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString()))
- assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString()))
- assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString()))
- assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString()))
- assertThat(logger.changes)
- .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
- assertThat(logger.changes)
- .contains(
- Pair(
- COL_ACTIVITY_DIRECTION_IN,
- connection.dataActivityDirection.hasActivityIn.toString(),
- )
- )
- assertThat(logger.changes)
- .contains(
- Pair(
- COL_ACTIVITY_DIRECTION_OUT,
- connection.dataActivityDirection.hasActivityOut.toString(),
- )
- )
- assertThat(logger.changes)
- .contains(
- Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
- )
- assertThat(logger.changes)
- .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString()))
- }
-
- @Test
- fun `log diff - primary level changes - only level is logged`() {
- val logger = TestLogger()
- val connectionOld = MobileConnectionModel(primaryLevel = 1)
-
- val connectionNew = MobileConnectionModel(primaryLevel = 2)
-
- connectionNew.logDiffs(connectionOld, logger)
-
- assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2")))
- }
-
- private class TestLogger : TableRowLogger {
- val changes = mutableListOf<Pair<String, String>>()
-
- override fun logChange(columnName: String, value: String?) {
- changes.add(Pair(columnName, value.toString()))
- }
-
- override fun logChange(columnName: String, value: Int) {
- changes.add(Pair(columnName, value.toString()))
- }
-
- override fun logChange(columnName: String, value: Boolean) {
- changes.add(Pair(columnName, value.toString()))
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 53cd71f1bdf9..44fbd5b99894 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,9 +17,11 @@
package com.android.systemui.statusbar.pipeline.mobile.data.repository
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
@@ -27,8 +29,19 @@ class FakeMobileConnectionRepository(
override val subId: Int,
override val tableLogBuffer: TableLogBuffer,
) : MobileConnectionRepository {
- private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
- override val connectionInfo = _connectionInfo
+ override val isEmergencyOnly = MutableStateFlow(false)
+ override val isRoaming = MutableStateFlow(false)
+ override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val isInService = MutableStateFlow(false)
+ override val isGsm = MutableStateFlow(false)
+ override val cdmaLevel = MutableStateFlow(0)
+ override val primaryLevel = MutableStateFlow(0)
+ override val dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+ override val dataActivityDirection =
+ MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+ override val carrierNetworkChangeActive = MutableStateFlow(false)
+ override val resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+ MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
@@ -40,10 +53,6 @@ class FakeMobileConnectionRepository(
override val networkName =
MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
- fun setConnectionInfo(model: MobileConnectionModel) {
- _connectionInfo.value = model
- }
-
fun setDataEnabled(enabled: Boolean) {
_dataEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index b072deedb9c9..37fac3458c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -25,7 +25,6 @@ import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -36,8 +35,11 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -123,34 +125,49 @@ internal class DemoMobileConnectionParameterizedTest(private val testCase: TestC
assertConnection(underTest, networkModel)
}
- private fun assertConnection(
+ private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+ val job = launch {
+ launch { conn.cdmaLevel.collect {} }
+ launch { conn.primaryLevel.collect {} }
+ launch { conn.dataActivityDirection.collect {} }
+ launch { conn.carrierNetworkChangeActive.collect {} }
+ launch { conn.isRoaming.collect {} }
+ launch { conn.networkName.collect {} }
+ launch { conn.isEmergencyOnly.collect {} }
+ launch { conn.dataConnectionState.collect {} }
+ }
+ return job
+ }
+
+ private fun TestScope.assertConnection(
conn: DemoMobileConnectionRepository,
model: FakeNetworkEventModel
) {
+ val job = startCollection(underTest)
when (model) {
is FakeNetworkEventModel.Mobile -> {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.value)
.isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
- assertThat(connectionInfo.carrierNetworkChangeActive)
+ assertThat(conn.carrierNetworkChangeActive.value)
.isEqualTo(model.carrierNetworkChange)
- assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
assertThat(conn.networkName.value)
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387): check these once we start handling them
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState)
- .isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
}
// MobileDisabled isn't combinatorial in nature, and is tested in
// DemoMobileConnectionsRepositoryTest.kt
else -> {}
}
+
+ job.cancel()
}
/** Matches [FakeNetworkEventModel] */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index f60d92bde202..0e45d8ea5563 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -26,7 +26,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBufferFactory
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
@@ -40,9 +39,11 @@ import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -524,47 +525,65 @@ class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
- private fun assertConnection(
+ private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+ val job = launch {
+ launch { conn.cdmaLevel.collect {} }
+ launch { conn.primaryLevel.collect {} }
+ launch { conn.dataActivityDirection.collect {} }
+ launch { conn.carrierNetworkChangeActive.collect {} }
+ launch { conn.isRoaming.collect {} }
+ launch { conn.networkName.collect {} }
+ launch { conn.isEmergencyOnly.collect {} }
+ launch { conn.dataConnectionState.collect {} }
+ }
+ return job
+ }
+
+ private fun TestScope.assertConnection(
conn: DemoMobileConnectionRepository,
- model: FakeNetworkEventModel
+ model: FakeNetworkEventModel,
) {
+ val job = startCollection(conn)
+ // Assert the fields using the `MutableStateFlow` so that we don't have to start up
+ // a collector for every field for every test
when (model) {
is FakeNetworkEventModel.Mobile -> {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
assertThat(conn.subId).isEqualTo(model.subId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.dataActivityDirection)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.dataActivityDirection.value)
.isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
- assertThat(connectionInfo.carrierNetworkChangeActive)
+ assertThat(conn.carrierNetworkChangeActive.value)
.isEqualTo(model.carrierNetworkChange)
- assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+ assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
assertThat(conn.networkName.value)
.isEqualTo(NetworkNameModel.IntentDerived(model.name))
// TODO(b/261029387) check these once we start handling them
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState)
- .isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
}
else -> {}
}
+
+ job.cancel()
}
- private fun assertCarrierMergedConnection(
+ private fun TestScope.assertCarrierMergedConnection(
conn: DemoMobileConnectionRepository,
model: FakeWifiEventModel.CarrierMerged,
) {
- val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+ val job = startCollection(conn)
assertThat(conn.subId).isEqualTo(model.subscriptionId)
- assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
- assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
- assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false)
- assertThat(connectionInfo.isRoaming).isEqualTo(false)
- assertThat(connectionInfo.isEmergencyOnly).isFalse()
- assertThat(connectionInfo.isGsm).isFalse()
- assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+ assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+ assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+ assertThat(conn.carrierNetworkChangeActive.value).isEqualTo(false)
+ assertThat(conn.isRoaming.value).isEqualTo(false)
+ assertThat(conn.isEmergencyOnly.value).isFalse()
+ assertThat(conn.isGsm.value).isFalse()
+ assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+ job.cancel()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index f0f213bc0d58..441186acb6b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -22,7 +22,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -75,36 +74,48 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
}
@Test
- fun connectionInfo_inactiveWifi_isDefault() =
+ fun inactiveWifi_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestConnState: DataConnectionState? = null
+ var latestNetType: ResolvedNetworkType? = null
+
+ val dataJob =
+ underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+ val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ dataJob.cancel()
+ netJob.cancel()
}
@Test
- fun connectionInfo_activeWifi_isDefault() =
+ fun activeWifi_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestConnState: DataConnectionState? = null
+ var latestNetType: ResolvedNetworkType? = null
+
+ val dataJob =
+ underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+ val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1))
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ dataJob.cancel()
+ netJob.cancel()
}
@Test
- fun connectionInfo_carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
+ fun carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
@@ -117,34 +128,16 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
)
)
- val expected =
- MobileConnectionModel(
- primaryLevel = 3,
- cdmaLevel = 3,
- dataConnectionState = DataConnectionState.Connected,
- dataActivityDirection =
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = false,
- ),
- resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
- isRoaming = false,
- isEmergencyOnly = false,
- operatorAlphaShort = null,
- isInService = true,
- isGsm = false,
- carrierNetworkChangeActive = false,
- )
- assertThat(latest).isEqualTo(expected)
+ assertThat(latest).isEqualTo(3)
job.cancel()
}
@Test
- fun connectionInfo_activity_comesFromWifiActivity() =
+ fun activity_comesFromWifiActivity() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataActivityModel? = null
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
@@ -162,8 +155,8 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
)
)
- assertThat(latest!!.dataActivityDirection.hasActivityIn).isTrue()
- assertThat(latest!!.dataActivityDirection.hasActivityOut).isFalse()
+ assertThat(latest!!.hasActivityIn).isTrue()
+ assertThat(latest!!.hasActivityOut).isFalse()
wifiRepository.setWifiActivity(
DataActivityModel(
@@ -172,17 +165,19 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
)
)
- assertThat(latest!!.dataActivityDirection.hasActivityIn).isFalse()
- assertThat(latest!!.dataActivityDirection.hasActivityOut).isTrue()
+ assertThat(latest!!.hasActivityIn).isFalse()
+ assertThat(latest!!.hasActivityOut).isTrue()
job.cancel()
}
@Test
- fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
+ fun carrierMergedWifi_wrongSubId_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestLevel: Int? = null
+ var latestType: ResolvedNetworkType? = null
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
+ val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -192,20 +187,19 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
)
)
- assertThat(latest).isEqualTo(MobileConnectionModel())
- assertThat(latest!!.primaryLevel).isNotEqualTo(3)
- assertThat(latest!!.resolvedNetworkType)
- .isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+ assertThat(latestLevel).isNotEqualTo(3)
+ assertThat(latestType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
- job.cancel()
+ levelJob.cancel()
+ typeJob.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
- fun connectionInfo_carrierMergedButNotEnabled_isDefault() =
+ fun carrierMergedButNotEnabled_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -216,17 +210,17 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
)
wifiRepository.setIsWifiEnabled(false)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latest).isNotEqualTo(3)
job.cancel()
}
// This scenario likely isn't possible, but write a test for it anyway
@Test
- fun connectionInfo_carrierMergedButWifiNotDefault_isDefault() =
+ fun carrierMergedButWifiNotDefault_isDefault() =
testScope.runTest {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
WifiNetworkModel.CarrierMerged(
@@ -237,7 +231,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
)
wifiRepository.setIsWifiDefault(false)
- assertThat(latest).isEqualTo(MobileConnectionModel())
+ assertThat(latest).isNotEqualTo(3)
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index cd4d8472763f..db5a7d1ad84a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -24,13 +24,12 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -94,16 +93,16 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun startingIsCarrierMerged_usesCarrierMergedInitially() =
testScope.runTest {
- val carrierMergedConnectionInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- )
- carrierMergedRepo.setConnectionInfo(carrierMergedConnectionInfo)
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Non-carrier-merged"
+
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+ mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
initializeRepo(startingIsCarrierMerged = true)
assertThat(underTest.activeRepo.value).isEqualTo(carrierMergedRepo)
- assertThat(underTest.connectionInfo.value).isEqualTo(carrierMergedConnectionInfo)
+ assertThat(underTest.operatorAlphaShort.value).isEqualTo(carrierMergedOperatorName)
verify(mobileFactory, never())
.build(
SUB_ID,
@@ -116,16 +115,16 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun startingNotCarrierMerged_usesTypicalInitially() =
testScope.runTest {
- val mobileConnectionInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Operator",
- )
- mobileRepo.setConnectionInfo(mobileConnectionInfo)
+ val carrierMergedOperatorName = "Carrier Merged Operator"
+ val nonCarrierMergedName = "Typical Operator"
+
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+ mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
initializeRepo(startingIsCarrierMerged = false)
assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
- assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
+ assertThat(underTest.operatorAlphaShort.value).isEqualTo(nonCarrierMergedName)
verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer)
}
@@ -156,39 +155,40 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
+
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
underTest.setIsCarrierMerged(true)
- val info1 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- primaryLevel = 1,
- )
- carrierMergedRepo.setConnectionInfo(info1)
+ val operator1 = "Carrier Merged Operator"
+ val level1 = 1
+ carrierMergedRepo.operatorAlphaShort.value = operator1
+ carrierMergedRepo.primaryLevel.value = level1
- assertThat(latest).isEqualTo(info1)
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
- val info2 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator #2",
- primaryLevel = 2,
- )
- carrierMergedRepo.setConnectionInfo(info2)
+ val operator2 = "Carrier Merged Operator #2"
+ val level2 = 2
+ carrierMergedRepo.operatorAlphaShort.value = operator2
+ carrierMergedRepo.primaryLevel.value = level2
- assertThat(latest).isEqualTo(info2)
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
- val info3 =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator #3",
- primaryLevel = 3,
- )
- carrierMergedRepo.setConnectionInfo(info3)
+ val operator3 = "Carrier Merged Operator #3"
+ val level3 = 3
+ carrierMergedRepo.operatorAlphaShort.value = operator3
+ carrierMergedRepo.primaryLevel.value = level3
- assertThat(latest).isEqualTo(info3)
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -196,39 +196,40 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
+
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
underTest.setIsCarrierMerged(false)
- val info1 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator",
- primaryLevel = 1,
- )
- mobileRepo.setConnectionInfo(info1)
+ val operator1 = "Typical Merged Operator"
+ val level1 = 1
+ mobileRepo.operatorAlphaShort.value = operator1
+ mobileRepo.primaryLevel.value = level1
- assertThat(latest).isEqualTo(info1)
+ assertThat(latestName).isEqualTo(operator1)
+ assertThat(latestLevel).isEqualTo(level1)
- val info2 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator #2",
- primaryLevel = 2,
- )
- mobileRepo.setConnectionInfo(info2)
+ val operator2 = "Typical Merged Operator #2"
+ val level2 = 2
+ mobileRepo.operatorAlphaShort.value = operator2
+ mobileRepo.primaryLevel.value = level2
- assertThat(latest).isEqualTo(info2)
+ assertThat(latestName).isEqualTo(operator2)
+ assertThat(latestLevel).isEqualTo(level2)
- val info3 =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Merged Operator #3",
- primaryLevel = 3,
- )
- mobileRepo.setConnectionInfo(info3)
+ val operator3 = "Typical Merged Operator #3"
+ val level3 = 3
+ mobileRepo.operatorAlphaShort.value = operator3
+ mobileRepo.primaryLevel.value = level3
- assertThat(latest).isEqualTo(info3)
+ assertThat(latestName).isEqualTo(operator3)
+ assertThat(latestLevel).isEqualTo(level3)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -236,57 +237,58 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
testScope.runTest {
initializeRepo(startingIsCarrierMerged = false)
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latestName: String? = null
+ var latestLevel: Int? = null
- val carrierMergedInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Carrier Merged Operator",
- primaryLevel = 4,
- )
- carrierMergedRepo.setConnectionInfo(carrierMergedInfo)
+ val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+ val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
- val mobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Typical Operator",
- primaryLevel = 2,
- )
- mobileRepo.setConnectionInfo(mobileInfo)
+ val carrierMergedOperator = "Carrier Merged Operator"
+ val carrierMergedLevel = 4
+ carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperator
+ carrierMergedRepo.primaryLevel.value = carrierMergedLevel
+
+ val mobileName = "Typical Operator"
+ val mobileLevel = 2
+ mobileRepo.operatorAlphaShort.value = mobileName
+ mobileRepo.primaryLevel.value = mobileLevel
// Start with the mobile info
- assertThat(latest).isEqualTo(mobileInfo)
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
// WHEN isCarrierMerged is set to true
underTest.setIsCarrierMerged(true)
// THEN the carrier merged info is used
- assertThat(latest).isEqualTo(carrierMergedInfo)
+ assertThat(latestName).isEqualTo(carrierMergedOperator)
+ assertThat(latestLevel).isEqualTo(carrierMergedLevel)
- val newCarrierMergedInfo =
- MobileConnectionModel(
- operatorAlphaShort = "New CM Operator",
- primaryLevel = 0,
- )
- carrierMergedRepo.setConnectionInfo(newCarrierMergedInfo)
+ val newCarrierMergedName = "New CM Operator"
+ val newCarrierMergedLevel = 0
+ carrierMergedRepo.operatorAlphaShort.value = newCarrierMergedName
+ carrierMergedRepo.primaryLevel.value = newCarrierMergedLevel
- assertThat(latest).isEqualTo(newCarrierMergedInfo)
+ assertThat(latestName).isEqualTo(newCarrierMergedName)
+ assertThat(latestLevel).isEqualTo(newCarrierMergedLevel)
// WHEN isCarrierMerged is set to false
underTest.setIsCarrierMerged(false)
// THEN the typical info is used
- assertThat(latest).isEqualTo(mobileInfo)
+ assertThat(latestName).isEqualTo(mobileName)
+ assertThat(latestLevel).isEqualTo(mobileLevel)
- val newMobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "New Mobile Operator",
- primaryLevel = 3,
- )
- mobileRepo.setConnectionInfo(newMobileInfo)
+ val newMobileName = "New MobileOperator"
+ val newMobileLevel = 3
+ mobileRepo.operatorAlphaShort.value = newMobileName
+ mobileRepo.primaryLevel.value = newMobileLevel
- assertThat(latest).isEqualTo(newMobileInfo)
+ assertThat(latestName).isEqualTo(newMobileName)
+ assertThat(latestLevel).isEqualTo(newMobileLevel)
- job.cancel()
+ nameJob.cancel()
+ levelJob.cancel()
}
@Test
@@ -370,7 +372,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val emergencyJob = underTest.isEmergencyOnly.launchIn(this)
+ val operatorJob = underTest.operatorAlphaShort.launchIn(this)
// WHEN we set up some mobile connection info
val serviceState = ServiceState()
@@ -394,7 +397,8 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
- job.cancel()
+ emergencyJob.cancel()
+ operatorJob.cancel()
}
@Test
@@ -409,7 +413,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
initializeRepo(startingIsCarrierMerged = true)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
// WHEN we set up carrier merged info
val networkId = 2
@@ -452,7 +456,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
// WHEN we set up some mobile connection info
val signalStrength = mock<SignalStrength>()
@@ -502,12 +506,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
// WHEN the normal network is updated
- val newMobileInfo =
- MobileConnectionModel(
- operatorAlphaShort = "Mobile Operator 2",
- primaryLevel = 0,
- )
- mobileRepo.setConnectionInfo(newMobileInfo)
+ mobileRepo.primaryLevel.value = 0
// THEN the new level is logged
assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
@@ -529,7 +528,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() {
// WHEN isCarrierMerged = false
initializeRepo(startingIsCarrierMerged = false)
- val job = underTest.connectionInfo.launchIn(this)
+ val job = underTest.primaryLevel.launchIn(this)
val signalStrength = mock<SignalStrength>()
whenever(signalStrength.level).thenReturn(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index bd5a4d7f7385..f6e595924f58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -50,13 +50,15 @@ import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
import android.telephony.TelephonyManager.EXTRA_SPN
import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
@@ -135,235 +137,285 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
@Test
- fun testFlowForSubId_default() =
+ fun emergencyOnly() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
-
- assertThat(latest).isEqualTo(MobileConnectionModel())
-
- job.cancel()
- }
-
- @Test
- fun testFlowForSubId_emergencyOnly() =
- runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
- assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+ assertThat(latest).isEqualTo(true)
job.cancel()
}
@Test
- fun testFlowForSubId_emergencyOnly_toggles() =
+ fun emergencyOnly_toggles() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<ServiceStateListener>()
val serviceState = ServiceState()
serviceState.isEmergencyOnly = true
callback.onServiceStateChanged(serviceState)
+ assertThat(latest).isTrue()
+
serviceState.isEmergencyOnly = false
callback.onServiceStateChanged(serviceState)
- assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun cdmaLevelUpdates() =
+ runBlocking(IMMEDIATE) {
+ var latest: Int? = null
+ val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(2)
+
+ // gsmLevel updates, no change to cdmaLevel
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+
+ assertThat(latest).isEqualTo(2)
job.cancel()
}
@Test
- fun testFlowForSubId_signalStrengths_levelsUpdate() =
+ fun gsmLevelUpdates() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Int? = null
+ val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
- val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
callback.onSignalStrengthsChanged(strength)
- assertThat(latest?.isGsm).isEqualTo(true)
- assertThat(latest?.primaryLevel).isEqualTo(1)
- assertThat(latest?.cdmaLevel).isEqualTo(2)
+ assertThat(latest).isEqualTo(1)
+
+ strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isEqualTo(3)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_connected() =
+ fun isGsm() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
+
+ val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+ var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isTrue()
+
+ strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = false)
+ callback.onSignalStrengthsChanged(strength)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun dataConnectionState_connected() =
+ runBlocking(IMMEDIATE) {
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+ assertThat(latest).isEqualTo(DataConnectionState.Connected)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_connecting() =
+ fun dataConnectionState_connecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting)
+ assertThat(latest).isEqualTo(DataConnectionState.Connecting)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_disconnected() =
+ fun dataConnectionState_disconnected() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected)
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnected)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_disconnecting() =
+ fun dataConnectionState_disconnecting() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting)
+ assertThat(latest).isEqualTo(DataConnectionState.Disconnecting)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_suspended() =
+ fun dataConnectionState_suspended() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Suspended)
+ assertThat(latest).isEqualTo(DataConnectionState.Suspended)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_handoverInProgress() =
+ fun dataConnectionState_handoverInProgress() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
- assertThat(latest?.dataConnectionState)
- .isEqualTo(DataConnectionState.HandoverInProgress)
+ assertThat(latest).isEqualTo(DataConnectionState.HandoverInProgress)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_unknown() =
+ fun dataConnectionState_unknown() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+ assertThat(latest).isEqualTo(DataConnectionState.Unknown)
job.cancel()
}
@Test
- fun testFlowForSubId_dataConnectionState_invalid() =
+ fun dataConnectionState_invalid() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataConnectionState? = null
+ val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
val callback =
getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
callback.onDataConnectionStateChanged(45, 200 /* unused */)
- assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Invalid)
+ assertThat(latest).isEqualTo(DataConnectionState.Invalid)
job.cancel()
}
@Test
- fun testFlowForSubId_dataActivity() =
+ fun dataActivity() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: DataActivityModel? = null
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<DataActivityListener>()
callback.onDataActivity(DATA_ACTIVITY_INOUT)
- assertThat(latest?.dataActivityDirection)
- .isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
+ assertThat(latest).isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
job.cancel()
}
@Test
- fun testFlowForSubId_carrierNetworkChange() =
+ fun carrierNetworkChange() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: Boolean? = null
+ val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
callback.onCarrierNetworkChange(true)
- assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+ assertThat(latest).isEqualTo(true)
+
+ job.cancel()
+ }
+
+ @Test
+ fun networkType_default() =
+ runBlocking(IMMEDIATE) {
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+ val expected = UnknownNetworkType
+
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_default() =
+ fun networkType_unknown_hasCorrectKey() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+ val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+ val type = NETWORK_TYPE_UNKNOWN
val expected = UnknownNetworkType
+ val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+ callback.onDisplayInfoChanged(ti)
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
+ assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type))
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_updatesUsingDefault() =
+ fun networkType_updatesUsingDefault() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = NETWORK_TYPE_LTE
@@ -371,16 +423,16 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
callback.onDisplayInfoChanged(ti)
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@Test
- fun subscriptionFlow_networkType_updatesUsingOverride() =
+ fun networkType_updatesUsingOverride() =
runBlocking(IMMEDIATE) {
- var latest: MobileConnectionModel? = null
- val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+ var latest: ResolvedNetworkType? = null
+ val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
val type = OVERRIDE_NETWORK_TYPE_LTE_CA
@@ -392,7 +444,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
callback.onDisplayInfoChanged(ti)
- assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+ assertThat(latest).isEqualTo(expected)
job.cancel()
}
@@ -466,7 +518,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
fun `roaming - gsm - queries service state`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isRoaming }.launchIn(this)
+ val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.roaming = false
@@ -492,8 +544,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
fun `activity - updates from callback`() =
runBlocking(IMMEDIATE) {
var latest: DataActivityModel? = null
- val job =
- underTest.connectionInfo.onEach { latest = it.dataActivityDirection }.launchIn(this)
+ val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
assertThat(latest)
.isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
@@ -611,8 +662,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
runBlocking(IMMEDIATE) {
var latest: String? = null
- val job =
- underTest.connectionInfo.onEach { latest = it.operatorAlphaShort }.launchIn(this)
+ val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
val shortName = "short name"
val serviceState = ServiceState()
@@ -633,7 +683,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
fun `connection model - isInService - not iwlan`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+ val job = underTest.isInService.onEach { latest = it }.launchIn(this)
val serviceState = ServiceState()
serviceState.voiceRegState = STATE_IN_SERVICE
@@ -658,7 +708,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+ val job = underTest.isInService.onEach { latest = it }.launchIn(this)
// Mock the service state here so we can make it specifically IWLAN
val serviceState: ServiceState = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index fa072fc366eb..1eb1056204cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,7 +23,6 @@ import com.android.settingslib.SignalIcon.MobileIconGroup
import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -74,9 +73,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun gsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(isGsm = true),
- )
+ connectionRepository.isGsm.value = true
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -89,13 +86,9 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun gsm_usesGsmLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL
- ),
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -108,13 +101,9 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL,
- ),
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
@@ -128,9 +117,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun notGsm_level_default_unknown() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(isGsm = false),
- )
+ connectionRepository.isGsm.value = false
var latest: Int? = null
val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -142,13 +129,9 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL
- ),
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = true
var latest: Int? = null
@@ -162,13 +145,9 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- primaryLevel = GSM_LEVEL,
- cdmaLevel = CDMA_LEVEL,
- ),
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.primaryLevel.value = GSM_LEVEL
+ connectionRepository.cdmaLevel.value = CDMA_LEVEL
mobileIconsInteractor.alwaysUseCdmaLevel.value = false
var latest: Int? = null
@@ -197,11 +176,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_three_g() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -214,23 +190,14 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_updates_on_change() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(
- mobileMappingsProxy.toIconKey(FOUR_G),
- ),
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
yield()
assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
@@ -241,12 +208,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_5g_override_type() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -259,12 +222,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_default_if_no_lookup() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType =
- DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)),
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -277,11 +236,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
@Test
fun iconGroup_carrierMerged_usesOverride() =
runBlocking(IMMEDIATE) {
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = CarrierMergedNetworkType,
- ),
- )
+ connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -295,11 +250,8 @@ class MobileIconInteractorTest : SysuiTestCase() {
fun `icon group - checks default data`() =
runBlocking(IMMEDIATE) {
mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
- ),
- )
+ connectionRepository.resolvedNetworkType.value =
+ DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
var latest: MobileIconGroup? = null
val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -380,9 +332,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(dataConnectionState = DataConnectionState.Connected)
- )
+ connectionRepository.dataConnectionState.value = DataConnectionState.Connected
yield()
assertThat(latest).isTrue()
@@ -396,9 +346,7 @@ class MobileIconInteractorTest : SysuiTestCase() {
var latest: Boolean? = null
val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected)
- )
+ connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected
assertThat(latest).isFalse()
@@ -411,11 +359,11 @@ class MobileIconInteractorTest : SysuiTestCase() {
var latest: Boolean? = null
val job = underTest.isInService.onEach { latest = it }.launchIn(this)
- connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = true))
+ connectionRepository.isInService.value = true
assertThat(latest).isTrue()
- connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = false))
+ connectionRepository.isInService.value = false
assertThat(latest).isFalse()
@@ -429,22 +377,13 @@ class MobileIconInteractorTest : SysuiTestCase() {
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = false,
- )
- )
+ connectionRepository.isGsm.value = true
+ connectionRepository.isRoaming.value = false
yield()
assertThat(latest).isFalse()
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = true,
- )
- )
+ connectionRepository.isRoaming.value = true
yield()
assertThat(latest).isTrue()
@@ -459,23 +398,15 @@ class MobileIconInteractorTest : SysuiTestCase() {
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = false
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = true,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = false,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = false
yield()
assertThat(latest).isTrue()
@@ -490,25 +421,15 @@ class MobileIconInteractorTest : SysuiTestCase() {
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = false,
- isRoaming = true,
- carrierNetworkChangeActive = true,
- )
- )
+ connectionRepository.isGsm.value = false
+ connectionRepository.isRoaming.value = true
+ connectionRepository.carrierNetworkChangeActive.value = true
yield()
assertThat(latest).isFalse()
connectionRepository.cdmaRoaming.value = true
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(
- isGsm = true,
- isRoaming = true,
- carrierNetworkChangeActive = true,
- )
- )
+ connectionRepository.isGsm.value = true
yield()
assertThat(latest).isFalse()
@@ -526,24 +447,20 @@ class MobileIconInteractorTest : SysuiTestCase() {
// Default network name, operator name is non-null, uses the operator name
connectionRepository.networkName.value = DEFAULT_NAME
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(operatorAlphaShort = testOperatorName)
- )
+ connectionRepository.operatorAlphaShort.value = testOperatorName
yield()
assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
// Default network name, operator name is null, uses the default
- connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
+ connectionRepository.operatorAlphaShort.value = null
yield()
assertThat(latest).isEqualTo(DEFAULT_NAME)
// Derived network name, operator name non-null, uses the derived name
connectionRepository.networkName.value = DERIVED_NAME
- connectionRepository.setConnectionInfo(
- MobileConnectionModel(operatorAlphaShort = testOperatorName)
- )
+ connectionRepository.operatorAlphaShort.value = testOperatorName
yield()
assertThat(latest).isEqualTo(DERIVED_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
index 5129f8584165..6980a0b4565e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -90,6 +90,12 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
}
@Test
+ fun testFrpNotActiveByDefault() {
+ init()
+ assertThat(controller.isFrpActive).isFalse()
+ }
+
+ @Test
fun testNotUserSetupByDefault() {
init()
assertThat(controller.isUserSetup(START_USER)).isFalse()
@@ -104,6 +110,14 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
}
@Test
+ fun testFrpActiveWhenCreated() {
+ settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+ init()
+
+ assertThat(controller.isFrpActive).isTrue()
+ }
+
+ @Test
fun testUserSetupWhenCreated() {
settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
init()
@@ -122,6 +136,16 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
}
@Test
+ fun testFrpActiveChange() {
+ init()
+
+ settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+ testableLooper.processAllMessages() // background observer
+
+ assertThat(controller.isFrpActive).isTrue()
+ }
+
+ @Test
fun testUserSetupChange() {
init()
@@ -164,6 +188,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
mainExecutor.runAllReady()
verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onFrpActiveChanged()
verify(listener, never()).onUserSetupChanged()
verify(listener, never()).onUserSwitched()
}
@@ -181,6 +206,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
verify(listener).onUserSwitched()
verify(listener, never()).onUserSetupChanged()
verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onFrpActiveChanged()
}
@Test
@@ -195,6 +221,7 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
verify(listener, never()).onUserSwitched()
verify(listener).onUserSetupChanged()
verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onFrpActiveChanged()
}
@Test
@@ -208,10 +235,26 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
verify(listener, never()).onUserSwitched()
verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onFrpActiveChanged()
verify(listener).onDeviceProvisionedChanged()
}
@Test
+ fun testListenerCalledOnFrpActiveChanged() {
+ init()
+ controller.addCallback(listener)
+
+ settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
+ testableLooper.processAllMessages()
+ mainExecutor.runAllReady()
+
+ verify(listener, never()).onUserSwitched()
+ verify(listener, never()).onUserSetupChanged()
+ verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener).onFrpActiveChanged()
+ }
+
+ @Test
fun testRemoveListener() {
init()
controller.addCallback(listener)
@@ -220,11 +263,13 @@ class DeviceProvisionedControllerImplTest : SysuiTestCase() {
switchUser(10)
settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER)
settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1)
+ settings.putInt(Settings.Secure.SECURE_FRP_MODE, 1)
testableLooper.processAllMessages()
mainExecutor.runAllReady()
verify(listener, never()).onDeviceProvisionedChanged()
+ verify(listener, never()).onFrpActiveChanged()
verify(listener, never()).onUserSetupChanged()
verify(listener, never()).onUserSwitched()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index d00acb89d228..3ed6cc88826c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -29,6 +29,8 @@ import android.os.UserManager
import android.provider.Settings
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.R
@@ -62,6 +64,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertNotNull
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -72,6 +75,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -96,6 +100,7 @@ class UserInteractorTest : SysuiTestCase() {
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: UserInteractor
@@ -154,6 +159,7 @@ class UserInteractorTest : SysuiTestCase() {
repository = telephonyRepository,
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
@@ -177,6 +183,18 @@ class UserInteractorTest : SysuiTestCase() {
}
@Test
+ fun `testKeyguardUpdateMonitor_onKeyguardGoingAway`() =
+ testScope.runTest {
+ val argumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ verify(keyguardUpdateMonitor).registerCallback(argumentCaptor.capture())
+
+ argumentCaptor.value.onKeyguardGoingAway()
+
+ val lastValue = collectLastValue(underTest.dialogDismissRequests)
+ assertNotNull(lastValue)
+ }
+
+ @Test
fun `onRecordSelected - user`() =
testScope.runTest {
val userInfos = createUserInfos(count = 3, includeGuest = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 22fc32af1b80..daa71b942a2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -25,6 +25,7 @@ import android.graphics.drawable.BitmapDrawable
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
@@ -80,6 +81,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: StatusBarUserChipViewModel
@@ -263,6 +265,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index a2bd8d365192..e08ebf4a9050 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,7 @@ import android.content.pm.UserInfo
import android.os.UserManager
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
@@ -81,6 +82,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: UserSwitcherViewModel
@@ -165,6 +167,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
repository = FakeTelephonyRepository(),
),
broadcastDispatcher = fakeBroadcastDispatcher,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 046ad1293521..f9bfafc13f35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.util.service;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -169,4 +171,19 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
verify(mCallback).onDisconnected(eq(connection),
eq(ObservableServiceConnection.DISCONNECT_REASON_UNBIND));
}
+
+ @Test
+ public void testBindServiceThrowsError() {
+ ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
+ mIntent, mExecutor, mTransformer);
+ connection.addCallback(mCallback);
+
+ when(mContext.bindService(eq(mIntent), anyInt(), eq(mExecutor), eq(connection)))
+ .thenThrow(new SecurityException());
+
+ // Verify that the exception was caught and that bind returns false, and we properly
+ // unbind.
+ assertThat(connection.bind()).isFalse();
+ verify(mContext).unbindService(connection);
+ }
}
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 8b579ac6539d..2292d9b60713 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -1127,6 +1127,14 @@ class AutomaticBrightnessController {
}
}
+ public float convertToFloatScale(float nits) {
+ if (mCurrentBrightnessMapper != null) {
+ return mCurrentBrightnessMapper.convertToFloatScale(nits);
+ } else {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+ }
+
public void recalculateSplines(boolean applyAdjustment, float[] adjustment) {
mCurrentBrightnessMapper.recalculateSplines(applyAdjustment, adjustment);
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 3fc50c4edf6d..d0471837d79b 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -322,6 +322,14 @@ public abstract class BrightnessMappingStrategy {
public abstract float convertToNits(float brightness);
/**
+ * Converts the provided nit value to a float scale value if possible.
+ *
+ * Returns {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if there's no available mapping for
+ * the nits to float scale.
+ */
+ public abstract float convertToFloatScale(float nits);
+
+ /**
* Adds a user interaction data point to the brightness mapping.
*
* This data point <b>must</b> exist on the brightness curve as a result of this call. This is
@@ -671,6 +679,11 @@ public abstract class BrightnessMappingStrategy {
}
@Override
+ public float convertToFloatScale(float nits) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ @Override
public void addUserDataPoint(float lux, float brightness) {
float unadjustedBrightness = getUnadjustedBrightness(lux);
if (mLoggingEnabled) {
@@ -913,6 +926,11 @@ public abstract class BrightnessMappingStrategy {
}
@Override
+ public float convertToFloatScale(float nits) {
+ return mNitsToBrightnessSpline.interpolate(nits);
+ }
+
+ @Override
public void addUserDataPoint(float lux, float brightness) {
float unadjustedBrightness = getUnadjustedBrightness(lux);
if (mLoggingEnabled) {
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 74486113dcf9..9982d2eb31d1 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -117,6 +117,23 @@ public class BrightnessSetting {
}
}
+ /**
+ * @return The brightness for the default display in nits. Used when the underlying display
+ * device has changed but we want to persist the nit value.
+ */
+ float getBrightnessNitsForDefaultDisplay() {
+ return mPersistentDataStore.getBrightnessNitsForDefaultDisplay();
+ }
+
+ /**
+ * Set brightness in nits for the default display. Used when we want to persist the nit value
+ * even if the underlying display device changes.
+ * @param nits The brightness value in nits
+ */
+ void setBrightnessNitsForDefaultDisplay(float nits) {
+ mPersistentDataStore.setBrightnessNitsForDefaultDisplay(nits);
+ }
+
private void notifyListeners(float brightness) {
for (BrightnessSettingListener l : mListeners) {
l.onBrightnessChanged(brightness);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 5ecc7f2b7bb8..25848002b936 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -233,6 +233,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// True if should use light sensor to automatically determine doze screen brightness.
private final boolean mAllowAutoBrightnessWhileDozingConfig;
+ // True if we want to persist the brightness value in nits even if the underlying display
+ // device changes.
+ private final boolean mPersistBrightnessNitsForDefaultDisplay;
+
// True if the brightness config has changed and the short-term model needs to be reset
private boolean mShouldResetShortTermModel;
@@ -583,6 +587,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
+ mPersistBrightnessNitsForDefaultDisplay = resources.getBoolean(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
+
mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
.getDisplayDeviceConfig();
@@ -651,7 +658,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
loadProximitySensor();
- mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ loadNitBasedBrightnessSetting();
mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -829,6 +836,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mDisplayStatsId = mUniqueDisplayId.hashCode();
mDisplayDeviceConfig = config;
loadFromDisplayDeviceConfig(token, info, hbmMetadata);
+ loadNitBasedBrightnessSetting();
/// Since the underlying display-device changed, we really don't know the
// last command that was sent to change it's state. Lets assume it is unknown so
@@ -873,10 +881,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mAutomaticBrightnessController.stop();
}
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.stop();
- }
-
if (mBrightnessSetting != null) {
mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
}
@@ -1125,6 +1129,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.stop();
+ mScreenOffBrightnessSensorController = null;
}
loadScreenOffBrightnessSensor();
int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux();
@@ -1242,6 +1247,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
mPowerState.stop();
mPowerState = null;
}
+
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.stop();
+ }
}
private void updatePowerState() {
@@ -2497,10 +2506,33 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return clampScreenBrightnessForVr(brightnessFloat);
}
+ private void loadNitBasedBrightnessSetting() {
+ if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+ float brightnessNitsForDefaultDisplay =
+ mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
+ if (brightnessNitsForDefaultDisplay >= 0) {
+ float brightnessForDefaultDisplay = convertToFloatScale(
+ brightnessNitsForDefaultDisplay);
+ if (isValidBrightnessValue(brightnessForDefaultDisplay)) {
+ mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
+ mCurrentScreenBrightnessSetting = brightnessForDefaultDisplay;
+ return;
+ }
+ }
+ }
+ mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ }
+
void setBrightness(float brightnessValue) {
// Update the setting, which will eventually call back into DPC to have us actually update
// the display with the new value.
mBrightnessSetting.setBrightness(brightnessValue);
+ if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+ float nits = convertToNits(brightnessValue);
+ if (nits >= 0) {
+ mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
+ }
+ }
}
void onBootCompleted() {
@@ -2513,7 +2545,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return;
}
setCurrentScreenBrightness(brightnessValue);
- mBrightnessSetting.setBrightness(brightnessValue);
+ setBrightness(brightnessValue);
}
private void setCurrentScreenBrightness(float brightnessValue) {
@@ -2592,6 +2624,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return mAutomaticBrightnessController.convertToNits(brightness);
}
+ private float convertToFloatScale(float nits) {
+ if (mAutomaticBrightnessController == null) {
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+ return mAutomaticBrightnessController.convertToFloatScale(nits);
+ }
+
@GuardedBy("mLock")
private void updatePendingProximityRequestsLocked() {
mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
@@ -2689,25 +2728,27 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
pw.println(" mScreenBrightnessForVrRangeMaximum=" + mScreenBrightnessForVrRangeMaximum);
pw.println(" mScreenBrightnessForVrDefault=" + mScreenBrightnessForVrDefault);
pw.println(" mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
- pw.println(" mAllowAutoBrightnessWhileDozingConfig=" +
- mAllowAutoBrightnessWhileDozingConfig);
+ pw.println(" mAllowAutoBrightnessWhileDozingConfig="
+ + mAllowAutoBrightnessWhileDozingConfig);
+ pw.println(" mPersistBrightnessNitsForDefaultDisplay="
+ + mPersistBrightnessNitsForDefaultDisplay);
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
synchronized (mCachedBrightnessInfo) {
- pw.println(" mCachedBrightnessInfo.brightness=" +
- mCachedBrightnessInfo.brightness.value);
- pw.println(" mCachedBrightnessInfo.adjustedBrightness=" +
- mCachedBrightnessInfo.adjustedBrightness.value);
- pw.println(" mCachedBrightnessInfo.brightnessMin=" +
- mCachedBrightnessInfo.brightnessMin.value);
- pw.println(" mCachedBrightnessInfo.brightnessMax=" +
- mCachedBrightnessInfo.brightnessMax.value);
+ pw.println(" mCachedBrightnessInfo.brightness="
+ + mCachedBrightnessInfo.brightness.value);
+ pw.println(" mCachedBrightnessInfo.adjustedBrightness="
+ + mCachedBrightnessInfo.adjustedBrightness.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMin="
+ + mCachedBrightnessInfo.brightnessMin.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMax="
+ + mCachedBrightnessInfo.brightnessMax.value);
pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode.value);
- pw.println(" mCachedBrightnessInfo.hbmTransitionPoint=" +
- mCachedBrightnessInfo.hbmTransitionPoint.value);
- pw.println(" mCachedBrightnessInfo.brightnessMaxReason =" +
- mCachedBrightnessInfo.brightnessMaxReason.value);
+ pw.println(" mCachedBrightnessInfo.hbmTransitionPoint="
+ + mCachedBrightnessInfo.hbmTransitionPoint.value);
+ pw.println(" mCachedBrightnessInfo.brightnessMaxReason ="
+ + mCachedBrightnessInfo.brightnessMaxReason.value);
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 73131a1dc220..a8e0d58cc4ff 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -94,6 +94,7 @@ import java.util.Objects;
* &lt;/brightness-curve>
* &lt;/brightness-configuration>
* &lt;/brightness-configurations>
+ * &lt;brightness-nits-for-default-display>600&lt;/brightness-nits-for-default-display>
* &lt;/display-manager-state>
* </code>
*
@@ -130,6 +131,9 @@ final class PersistentDataStore {
private static final String TAG_RESOLUTION_HEIGHT = "resolution-height";
private static final String TAG_REFRESH_RATE = "refresh-rate";
+ private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
+ "brightness-nits-for-default-display";
+
// Remembered Wifi display devices.
private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
@@ -137,6 +141,8 @@ final class PersistentDataStore {
private final HashMap<String, DisplayState> mDisplayStates =
new HashMap<String, DisplayState>();
+ private float mBrightnessNitsForDefaultDisplay = -1;
+
// Display values which should be stable across the device's lifetime.
private final StableDeviceValues mStableDeviceValues = new StableDeviceValues();
@@ -312,6 +318,19 @@ final class PersistentDataStore {
return false;
}
+ public float getBrightnessNitsForDefaultDisplay() {
+ return mBrightnessNitsForDefaultDisplay;
+ }
+
+ public boolean setBrightnessNitsForDefaultDisplay(float nits) {
+ if (nits != mBrightnessNitsForDefaultDisplay) {
+ mBrightnessNitsForDefaultDisplay = nits;
+ setDirty();
+ return true;
+ }
+ return false;
+ }
+
public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) {
final String displayDeviceUniqueId = displayDevice.getUniqueId();
if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
@@ -513,6 +532,10 @@ final class PersistentDataStore {
if (parser.getName().equals(TAG_BRIGHTNESS_CONFIGURATIONS)) {
mGlobalBrightnessConfigurations.loadFromXml(parser);
}
+ if (parser.getName().equals(TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY)) {
+ String value = parser.nextText();
+ mBrightnessNitsForDefaultDisplay = Float.parseFloat(value);
+ }
}
}
@@ -592,6 +615,9 @@ final class PersistentDataStore {
serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
mGlobalBrightnessConfigurations.saveToXml(serializer);
serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
+ serializer.startTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
+ serializer.text(Float.toString(mBrightnessNitsForDefaultDisplay));
+ serializer.endTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
serializer.endTag(null, TAG_DISPLAY_MANAGER_STATE);
serializer.endDocument();
}
@@ -615,6 +641,7 @@ final class PersistentDataStore {
mStableDeviceValues.dump(pw, " ");
pw.println(" GlobalBrightnessConfigurations:");
mGlobalBrightnessConfigurations.dump(pw, " ");
+ pw.println(" mBrightnessNitsForDefaultDisplay=" + mBrightnessNitsForDefaultDisplay);
}
private static final class DisplayState {
diff --git a/services/core/java/com/android/server/media/projection/OWNERS b/services/core/java/com/android/server/media/projection/OWNERS
index 9ca391013aa3..832bcd9d70e6 100644
--- a/services/core/java/com/android/server/media/projection/OWNERS
+++ b/services/core/java/com/android/server/media/projection/OWNERS
@@ -1,2 +1 @@
-michaelwr@google.com
-santoscordon@google.com
+include /media/java/android/media/projection/OWNERS
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ff10cbc19d5f..d11413937f8d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
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.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
@@ -6323,21 +6324,40 @@ public class NotificationManagerService extends SystemService {
checkCallerIsSystem();
mHandler.post(() -> {
synchronized (mNotificationLock) {
- // strip flag from all enqueued notifications. listeners will be informed
- // in post runnable.
- List<NotificationRecord> enqueued = findNotificationsByListLocked(
- mEnqueuedNotifications, pkg, null, notificationId, userId);
- for (int i = 0; i < enqueued.size(); i++) {
- removeForegroundServiceFlagLocked(enqueued.get(i));
- }
+ int count = getNotificationCount(pkg, userId);
+ boolean removeFgsNotification = false;
+ if (count > MAX_PACKAGE_NOTIFICATIONS) {
+ mUsageStats.registerOverCountQuota(pkg);
+ removeFgsNotification = true;
+ }
+ if (removeFgsNotification) {
+ NotificationRecord r = findNotificationLocked(pkg, null, notificationId,
+ userId);
+ if (r != null) {
+ if (DBG) {
+ Slog.d(TAG, "Remove FGS flag not allow. Cancel FGS notification");
+ }
+ removeFromNotificationListsLocked(r);
+ cancelNotificationLocked(r, false, REASON_APP_CANCEL, true,
+ null, SystemClock.elapsedRealtime());
+ }
+ } else {
+ // strip flag from all enqueued notifications. listeners will be informed
+ // in post runnable.
+ List<NotificationRecord> enqueued = findNotificationsByListLocked(
+ mEnqueuedNotifications, pkg, null, notificationId, userId);
+ for (int i = 0; i < enqueued.size(); i++) {
+ removeForegroundServiceFlagLocked(enqueued.get(i));
+ }
- // if posted notification exists, strip its flag and tell listeners
- NotificationRecord r = findNotificationByListLocked(
- mNotificationList, pkg, null, notificationId, userId);
- if (r != null) {
- removeForegroundServiceFlagLocked(r);
- mRankingHelper.sort(mNotificationList);
- mListeners.notifyPostedLocked(r, r);
+ // if posted notification exists, strip its flag and tell listeners
+ NotificationRecord r = findNotificationByListLocked(
+ mNotificationList, pkg, null, notificationId, userId);
+ if (r != null) {
+ removeForegroundServiceFlagLocked(r);
+ mRankingHelper.sort(mNotificationList);
+ mListeners.notifyPostedLocked(r, r);
+ }
}
}
});
@@ -6483,9 +6503,17 @@ public class NotificationManagerService extends SystemService {
checkRestrictedCategories(notification);
+ // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE,
+ // but it's also possible that the app has called notify() with an update to an
+ // FGS notification that hasn't yet been displayed. Make sure we check for any
+ // FGS-related situation up front, outside of any locks so it's safe to call into
+ // the Activity Manager.
+ final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(
+ notification, tag, id, pkg, userId);
+
// Fix the notification as best we can.
try {
- fixNotification(notification, pkg, tag, id, userId);
+ fixNotification(notification, pkg, tag, id, userId, notificationUid, policy);
} catch (Exception e) {
if (notification.isForegroundService()) {
throw new SecurityException("Invalid FGS notification", e);
@@ -6494,13 +6522,7 @@ public class NotificationManagerService extends SystemService {
return;
}
- // Notifications passed to setForegroundService() have FLAG_FOREGROUND_SERVICE,
- // but it's also possible that the app has called notify() with an update to an
- // FGS notification that hasn't yet been displayed. Make sure we check for any
- // FGS-related situation up front, outside of any locks so it's safe to call into
- // the Activity Manager.
- final ServiceNotificationPolicy policy = mAmi.applyForegroundServiceNotification(
- notification, tag, id, pkg, userId);
+
if (policy == ServiceNotificationPolicy.UPDATE_ONLY) {
// Proceed if the notification is already showing/known, otherwise ignore
// because the service lifecycle logic has retained responsibility for its
@@ -6663,14 +6685,20 @@ public class NotificationManagerService extends SystemService {
@VisibleForTesting
protected void fixNotification(Notification notification, String pkg, String tag, int id,
- int userId) throws NameNotFoundException, RemoteException {
+ @UserIdInt int userId, int notificationUid, ServiceNotificationPolicy fgsPolicy)
+ throws NameNotFoundException, RemoteException {
final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
(userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId);
Notification.addFieldsFromContext(ai, notification);
- int canColorize = mPackageManagerClient.checkPermission(
- android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
+ if (notification.isForegroundService() && fgsPolicy == NOT_FOREGROUND_SERVICE) {
+ notification.flags &= ~FLAG_FOREGROUND_SERVICE;
+ }
+
+ int canColorize = getContext().checkPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, -1, notificationUid);
+
if (canColorize == PERMISSION_GRANTED) {
notification.flags |= Notification.FLAG_CAN_COLORIZE;
} else {
@@ -7059,6 +7087,29 @@ public class NotificationManagerService extends SystemService {
return mPermissionHelper.hasPermission(uid);
}
+ private int getNotificationCount(String pkg, int userId) {
+ int count = 0;
+ synchronized (mNotificationLock) {
+ final int numListSize = mNotificationList.size();
+ for (int i = 0; i < numListSize; i++) {
+ final NotificationRecord existing = mNotificationList.get(i);
+ if (existing.getSbn().getPackageName().equals(pkg)
+ && existing.getSbn().getUserId() == userId) {
+ count++;
+ }
+ }
+ final int numEnqSize = mEnqueuedNotifications.size();
+ for (int i = 0; i < numEnqSize; i++) {
+ final NotificationRecord existing = mEnqueuedNotifications.get(i);
+ if (existing.getSbn().getPackageName().equals(pkg)
+ && existing.getSbn().getUserId() == userId) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
protected int getNotificationCount(String pkg, int userId, int excludedId,
String excludedTag) {
int count = 0;
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 7fc46fd6f757..a2b2983a8f35 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1641,7 +1641,7 @@ public class ShortcutService extends IShortcutService.Stub {
return false;
}
int uid = injectGetPackageUid(systemChooser.getPackageName(), UserHandle.USER_SYSTEM);
- return uid == callingUid;
+ return UserHandle.getAppId(uid) == UserHandle.getAppId(callingUid);
}
private void enforceSystemOrShell() {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f913cef99813..bb6db9aa4993 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -199,6 +199,7 @@ import com.android.internal.policy.PhoneWindow;
import com.android.internal.policy.TransitionAnimation;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.ExtconStateObserver;
import com.android.server.ExtconUEventObserver;
import com.android.server.GestureLauncherService;
@@ -407,6 +408,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
AppOpsManager mAppOpsManager;
PackageManager mPackageManager;
SideFpsEventHandler mSideFpsEventHandler;
+ LockPatternUtils mLockPatternUtils;
private boolean mHasFeatureAuto;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -1056,8 +1058,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
synchronized (mLock) {
- // Lock the device after the dream transition has finished.
- mLockAfterAppTransitionFinished = true;
+ // If the setting to lock instantly on power button press is true, then set the flag to
+ // lock after the dream transition has finished.
+ mLockAfterAppTransitionFinished =
+ mLockPatternUtils.getPowerButtonInstantlyLocks(mCurrentUserId);
}
dreamManagerInternal.requestDream();
@@ -1929,6 +1933,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mHasFeatureHdmiCec = mPackageManager.hasSystemFeature(FEATURE_HDMI_CEC);
mAccessibilityShortcutController =
new AccessibilityShortcutController(mContext, new Handler(), mCurrentUserId);
+ mLockPatternUtils = new LockPatternUtils(mContext);
mLogger = new MetricsLogger();
mScreenOffSleepTokenAcquirer = mActivityTaskManagerInternal
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b4b8cf9a9eab..43fb44ca02af 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -169,6 +169,7 @@ import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.IProcessStats;
import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.app.procstats.StatsEventOutput;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelAllocationStats;
@@ -612,12 +613,19 @@ public class StatsPullAtomService extends SystemService {
}
case FrameworkStatsLog.PROC_STATS:
synchronized (mProcStatsLock) {
- return pullProcStatsLocked(ProcessStats.REPORT_ALL, atomTag, data);
+ return pullProcStatsLocked(atomTag, data);
}
case FrameworkStatsLog.PROC_STATS_PKG_PROC:
synchronized (mProcStatsLock) {
- return pullProcStatsLocked(ProcessStats.REPORT_PKG_PROC_STATS, atomTag,
- data);
+ return pullProcStatsLocked(atomTag, data);
+ }
+ case FrameworkStatsLog.PROCESS_STATE:
+ synchronized (mProcStatsLock) {
+ return pullProcessStateLocked(atomTag, data);
+ }
+ case FrameworkStatsLog.PROCESS_ASSOCIATION:
+ synchronized (mProcStatsLock) {
+ return pullProcessAssociationLocked(atomTag, data);
}
case FrameworkStatsLog.DISK_IO:
synchronized (mDiskIoLock) {
@@ -890,6 +898,8 @@ public class StatsPullAtomService extends SystemService {
registerNumFacesEnrolled();
registerProcStats();
registerProcStatsPkgProc();
+ registerProcessState();
+ registerProcessAssociation();
registerDiskIO();
registerPowerProfile();
registerProcessCpuTime();
@@ -2870,59 +2880,138 @@ public class StatsPullAtomService extends SystemService {
);
}
- private int pullProcStatsLocked(int section, int atomTag, List<StatsEvent> pulledData) {
+ private void registerProcessState() {
+ int tagId = FrameworkStatsLog.PROCESS_STATE;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl);
+ }
+
+ private void registerProcessAssociation() {
+ int tagId = FrameworkStatsLog.PROCESS_ASSOCIATION;
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ DIRECT_EXECUTOR,
+ mStatsCallbackImpl);
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private ProcessStats getStatsFromProcessStatsService(int atomTag) {
IProcessStats processStatsService = getIProcessStatsService();
if (processStatsService == null) {
- return StatsManager.PULL_SKIP;
+ return null;
}
-
final long token = Binder.clearCallingIdentity();
try {
// force procstats to flush & combine old files into one store
- long lastHighWaterMark = readProcStatsHighWaterMark(section);
-
- ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
- for (int i = 0; i < protoStreams.length; i++) {
- protoStreams[i] = new ProtoOutputStream();
- }
-
+ long lastHighWaterMark = readProcStatsHighWaterMark(atomTag);
ProcessStats procStats = new ProcessStats(false);
// Force processStatsService to aggregate all in-storage and in-memory data.
- long highWaterMark = processStatsService.getCommittedStatsMerged(
- lastHighWaterMark, section, true, null, procStats);
- procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
-
- for (int i = 0; i < protoStreams.length; i++) {
- byte[] bytes = protoStreams[i].getBytes(); // cache the value
- if (bytes.length > 0) {
- pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, bytes,
- // This is a shard ID, and is specified in the metric definition to be
- // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE to
- // keep all the shards, as it thinks each shard is a different dimension
- // of data.
- i));
- }
- }
-
- new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + lastHighWaterMark)
+ long highWaterMark =
+ processStatsService.getCommittedStatsMerged(
+ lastHighWaterMark,
+ ProcessStats.REPORT_ALL, // ignored since committedStats below is null.
+ true,
+ null, // committedStats
+ procStats);
+ new File(
+ mBaseDir.getAbsolutePath()
+ + "/"
+ + highWaterMarkFilePrefix(atomTag)
+ + "_"
+ + lastHighWaterMark)
.delete();
- new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + highWaterMark)
+ new File(
+ mBaseDir.getAbsolutePath()
+ + "/"
+ + highWaterMarkFilePrefix(atomTag)
+ + "_"
+ + highWaterMark)
.createNewFile();
+ return procStats;
} catch (RemoteException | IOException e) {
Slog.e(TAG, "Getting procstats failed: ", e);
- return StatsManager.PULL_SKIP;
+ return null;
} finally {
Binder.restoreCallingIdentity(token);
}
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private int pullProcStatsLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
+ for (int i = 0; i < protoStreams.length; i++) {
+ protoStreams[i] = new ProtoOutputStream();
+ }
+ procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
+ for (int i = 0; i < protoStreams.length; i++) {
+ byte[] bytes = protoStreams[i].getBytes(); // cache the value
+ if (bytes.length > 0) {
+ pulledData.add(
+ FrameworkStatsLog.buildStatsEvent(
+ atomTag,
+ bytes,
+ // This is a shard ID, and is specified in the metric definition to
+ // be
+ // a dimension. This will result in statsd using RANDOM_ONE_SAMPLE
+ // to
+ // keep all the shards, as it thinks each shard is a different
+ // dimension
+ // of data.
+ i));
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private int pullProcessStateLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ procStats.dumpProcessState(atomTag, new StatsEventOutput(pulledData));
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ @GuardedBy("mProcStatsLock")
+ private int pullProcessAssociationLocked(int atomTag, List<StatsEvent> pulledData) {
+ ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
+ if (procStats == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ procStats.dumpProcessAssociation(atomTag, new StatsEventOutput(pulledData));
return StatsManager.PULL_SUCCESS;
}
+ private String highWaterMarkFilePrefix(int atomTag) {
+ // For backward compatibility, use the legacy ProcessStats enum value as the prefix for
+ // PROC_STATS and PROC_STATS_PKG_PROC.
+ if (atomTag == FrameworkStatsLog.PROC_STATS) {
+ return String.valueOf(ProcessStats.REPORT_ALL);
+ }
+ if (atomTag == FrameworkStatsLog.PROC_STATS_PKG_PROC) {
+ return String.valueOf(ProcessStats.REPORT_PKG_PROC_STATS);
+ }
+ return "atom-" + atomTag;
+ }
+
// read high watermark for section
- private long readProcStatsHighWaterMark(int section) {
+ private long readProcStatsHighWaterMark(int atomTag) {
try {
- File[] files = mBaseDir.listFiles((d, name) -> {
- return name.toLowerCase().startsWith(String.valueOf(section) + '_');
- });
+ File[] files =
+ mBaseDir.listFiles(
+ (d, name) -> {
+ return name.toLowerCase()
+ .startsWith(highWaterMarkFilePrefix(atomTag) + '_');
+ });
if (files == null || files.length == 0) {
return 0;
}
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 7489f80946eb..7c9244e39e67 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -696,6 +696,8 @@ class ActivityClientController extends IActivityClientController.Stub {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
if (r != null) {
+ EventLogTags.writeWmSetRequestedOrientation(requestedOrientation,
+ r.shortComponentName);
r.setRequestedOrientation(requestedOrientation);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 61f320337e3a..681d5b6e34fb 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1487,6 +1487,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLastReportedMultiWindowMode = inPictureInPictureMode;
ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
true /* ignoreVisibility */);
+ if (inPictureInPictureMode && findMainWindow() == null) {
+ // Prevent malicious app entering PiP without valid WindowState, which can in turn
+ // result a non-touchable PiP window since the InputConsumer for PiP requires it.
+ EventLog.writeEvent(0x534e4554, "265293293", -1, "");
+ removeImmediately();
+ }
}
}
@@ -7727,27 +7733,38 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/**
* Returns the requested {@link Configuration.Orientation} for the current activity.
+ */
+ @Configuration.Orientation
+ @Override
+ int getRequestedConfigurationOrientation(boolean forDisplay) {
+ return getRequestedConfigurationOrientation(forDisplay, getOverrideOrientation());
+ }
+
+ /**
+ * Returns the requested {@link Configuration.Orientation} for the requested
+ * {@link ActivityInfo.ScreenOrientation}.
*
- * <p>When The current orientation is set to {@link SCREEN_ORIENTATION_BEHIND} it returns the
- * requested orientation for the activity below which is the first activity with an explicit
+ * <p>When the current screen orientation is set to {@link SCREEN_ORIENTATION_BEHIND} it returns
+ * the requested orientation for the activity below which is the first activity with an explicit
* (different from {@link SCREEN_ORIENTATION_UNSET}) orientation which is not {@link
* SCREEN_ORIENTATION_BEHIND}.
*/
@Configuration.Orientation
- @Override
- int getRequestedConfigurationOrientation(boolean forDisplay) {
+ int getRequestedConfigurationOrientation(boolean forDisplay,
+ @ActivityInfo.ScreenOrientation int requestedOrientation) {
if (mLetterboxUiController.hasInheritedOrientation()) {
final RootDisplayArea root = getRootDisplayArea();
if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
- return ActivityInfo.reverseOrientation(
+ return reverseConfigurationOrientation(
mLetterboxUiController.getInheritedOrientation());
} else {
return mLetterboxUiController.getInheritedOrientation();
}
}
- if (task != null && getOverrideOrientation() == SCREEN_ORIENTATION_BEHIND) {
+ if (task != null && requestedOrientation == SCREEN_ORIENTATION_BEHIND) {
// We use Task here because we want to be consistent with what happens in
// multi-window mode where other tasks orientations are ignored.
+ android.util.Log.d("orientation", "We are here");
final ActivityRecord belowCandidate = task.getActivity(
a -> a.canDefineOrientationForActivitiesAbove() /* callback */,
this /* boundary */, false /* includeBoundary */,
@@ -7756,7 +7773,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return belowCandidate.getRequestedConfigurationOrientation(forDisplay);
}
}
- return super.getRequestedConfigurationOrientation(forDisplay);
+ return super.getRequestedConfigurationOrientation(forDisplay, requestedOrientation);
+ }
+
+ /**
+ * Returns the reversed configuration orientation.
+ * @hide
+ */
+ @Configuration.Orientation
+ public static int reverseConfigurationOrientation(@Configuration.Orientation int orientation) {
+ switch (orientation) {
+ case ORIENTATION_LANDSCAPE:
+ return ORIENTATION_PORTRAIT;
+ case ORIENTATION_PORTRAIT:
+ return ORIENTATION_LANDSCAPE;
+ default:
+ return orientation;
+ }
}
/**
@@ -7802,6 +7835,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) {
return;
}
+ // This is necessary in order to avoid going into size compat mode when the orientation
+ // change request comes from the app
+ if (mWmService.mLetterboxConfiguration
+ .isSizeCompatModeDisabledAfterOrientationChangeFromApp()
+ && getRequestedConfigurationOrientation(false, requestedOrientation)
+ != getRequestedConfigurationOrientation(false /*forDisplay */)) {
+ // Do not change the requested configuration now, because this will be done when setting
+ // the orientation below with the new mCompatDisplayInsets
+ clearSizeCompatModeAttributes();
+ }
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Setting requested orientation %s for %s",
+ ActivityInfo.screenOrientationToString(requestedOrientation), this);
setOrientation(requestedOrientation, this);
// Push the new configuration to the requested app in case where it's not pushed, e.g. when
@@ -8021,17 +8067,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mDisplayContent, this, mLetterboxBoundsForFixedOrientationAndAspectRatio);
}
- @VisibleForTesting
- void clearSizeCompatMode() {
- final float lastSizeCompatScale = mSizeCompatScale;
+ private void clearSizeCompatModeAttributes() {
mInSizeCompatModeForBounds = false;
mSizeCompatScale = 1f;
mSizeCompatBounds = null;
mCompatDisplayInsets = null;
+ }
+
+ @VisibleForTesting
+ void clearSizeCompatMode() {
+ final float lastSizeCompatScale = mSizeCompatScale;
+ clearSizeCompatModeAttributes();
if (mSizeCompatScale != lastSizeCompatScale) {
forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
}
-
// Clear config override in #updateCompatDisplayInsets().
final int activityType = getActivityType();
final Configuration overrideConfig = getRequestedOverrideConfiguration();
@@ -8099,9 +8148,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (isFixedOrientationLetterboxAllowed) {
resolveFixedOrientationConfiguration(newParentConfiguration);
}
-
- if (getCompatDisplayInsets() != null) {
- resolveSizeCompatModeConfiguration(newParentConfiguration);
+ final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
+ if (compatDisplayInsets != null) {
+ resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets);
} else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
// We ignore activities' requested orientation in multi-window modes. They may be
// taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8118,7 +8167,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
resolveAspectRatioRestriction(newParentConfiguration);
}
- if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null
+ if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
// In fullscreen, can be letterboxed for aspect ratio.
|| !inMultiWindowMode()) {
updateResolvedBoundsPosition(newParentConfiguration);
@@ -8126,7 +8175,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
boolean isIgnoreOrientationRequest = mDisplayContent != null
&& mDisplayContent.getIgnoreOrientationRequest();
- if (getCompatDisplayInsets() == null
+ if (compatDisplayInsets == null
// for size compat mode set in updateCompatDisplayInsets
// Fixed orientation letterboxing is possible on both large screen devices
// with ignoreOrientationRequest enabled and on phones in split screen even with
@@ -8173,7 +8222,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
!matchParentBounds(),
- getCompatDisplayInsets() != null,
+ compatDisplayInsets != null,
shouldCreateCompatDisplayInsets());
}
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8581,7 +8630,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* Resolves consistent screen configuration for orientation and rotation changes without
* inheriting the parent bounds.
*/
- private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) {
+ private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
+ @NonNull CompatDisplayInsets compatDisplayInsets) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
@@ -8602,13 +8652,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
? requestedOrientation
// We should use the original orientation of the activity when possible to avoid
// forcing the activity in the opposite orientation.
- : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
- ? getCompatDisplayInsets().mOriginalRequestedOrientation
+ : compatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+ ? compatDisplayInsets.mOriginalRequestedOrientation
: newParentConfiguration.orientation;
int rotation = newParentConfiguration.windowConfiguration.getRotation();
final boolean isFixedToUserRotation = mDisplayContent == null
|| mDisplayContent.getDisplayRotation().isFixedToUserRotation();
- if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) {
+ if (!isFixedToUserRotation && !compatDisplayInsets.mIsFloating) {
// Use parent rotation because the original display can be rotated.
resolvedConfig.windowConfiguration.setRotation(rotation);
} else {
@@ -8624,11 +8674,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// rely on them to contain the original and unchanging width and height of the app.
final Rect containingAppBounds = new Rect();
final Rect containingBounds = mTmpBounds;
- getCompatDisplayInsets().getContainerBounds(containingAppBounds, containingBounds, rotation,
+ compatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
orientation, orientationRequested, isFixedToUserRotation);
resolvedBounds.set(containingBounds);
// The size of floating task is fixed (only swap), so the aspect ratio is already correct.
- if (!getCompatDisplayInsets().mIsFloating) {
+ if (!compatDisplayInsets.mIsFloating) {
mIsAspectRatioApplied =
applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
}
@@ -8637,7 +8687,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// are calculated in compat container space. The actual position on screen will be applied
// later, so the calculation is simpler that doesn't need to involve offset from parent.
getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
- getCompatDisplayInsets());
+ compatDisplayInsets);
// Use current screen layout as source because the size of app is independent to parent.
resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 491e58b63f76..c527310abb14 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1486,7 +1486,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
a.persistableMode = ActivityInfo.PERSIST_NEVER;
a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
- a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | ActivityInfo.FLAG_NO_HISTORY;
+ a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
a.configChanges = 0xffffffff;
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 1e5a219e5e52..48ce22e227dd 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
option java_package com.android.server.wm
@@ -62,6 +62,10 @@ option java_package com.android.server.wm
31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
# Task removed with source explanation.
31003 wm_task_removed (TaskId|1|5),(Reason|3)
+
+# Set the requested orientation of an activity.
+31006 wm_set_requested_orientation (Orientation|1|5),(Component Name|3)
+
# bootanim finished:
31007 wm_boot_animation_done (time|2|3)
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index fa49a6ba6c2b..37cf5bc95a23 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW_IGNORE_ORIENTATION_REQUEST;
+import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS;
import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY;
@@ -314,6 +315,10 @@ final class LetterboxConfiguration {
mDeviceConfig.updateFlagActiveStatus(
/* isActive */ mTranslucentLetterboxingEnabled,
/* key */ KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY);
+ mDeviceConfig.updateFlagActiveStatus(
+ /* isActive */ true,
+ /* key */ KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP);
+
mLetterboxConfigurationPersister = letterboxConfigurationPersister;
mLetterboxConfigurationPersister.start();
}
@@ -327,6 +332,16 @@ final class LetterboxConfiguration {
}
/**
+ * Whether size compat mode is disabled after an orientation change request comes from the app.
+ * This value is controlled via {@link android.provider.DeviceConfig}.
+ */
+ // TODO(b/270356567) Clean up this flag
+ boolean isSizeCompatModeDisabledAfterOrientationChangeFromApp() {
+ return mDeviceConfig.getFlag(
+ KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP);
+ }
+
+ /**
* Overrides the aspect ratio of letterbox for fixed orientation. If given value is <= {@link
* #MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO}, both it and a value of {@link
* com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
index df3c8f0fdccc..1651af328b1f 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.provider.DeviceConfig;
import android.util.ArraySet;
-
import com.android.internal.annotations.VisibleForTesting;
import java.util.Map;
@@ -53,6 +52,11 @@ final class LetterboxConfigurationDeviceConfig
private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true;
+ static final String KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP =
+ "disable_size_compat_mode_after_orientation_change_from_app";
+ private static final boolean
+ DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP = true;
+
@VisibleForTesting
static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of(
KEY_ENABLE_CAMERA_COMPAT_TREATMENT,
@@ -64,7 +68,9 @@ final class LetterboxConfigurationDeviceConfig
KEY_ENABLE_COMPAT_FAKE_FOCUS,
DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS,
KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
- DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY
+ DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY,
+ KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP,
+ DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP
);
// Whether camera compatibility treatment is enabled.
@@ -93,6 +99,10 @@ final class LetterboxConfigurationDeviceConfig
private boolean mIsTranslucentLetterboxingAllowed =
DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY;
+ // Whether size compat mode is disabled after an orientation change request comes from the app
+ private boolean mIsSizeCompatModeDisabledAfterOrientationChangeFromApp =
+ DEFAULT_VALUE_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP;
+
// Set of active device configs that need to be updated in
// DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged.
private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>();
@@ -142,6 +152,8 @@ final class LetterboxConfigurationDeviceConfig
return mIsCompatFakeFocusAllowed;
case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
return mIsTranslucentLetterboxingAllowed;
+ case KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP:
+ return mIsSizeCompatModeDisabledAfterOrientationChangeFromApp;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
@@ -169,6 +181,10 @@ final class LetterboxConfigurationDeviceConfig
case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY:
mIsTranslucentLetterboxingAllowed = getDeviceConfig(key, defaultValue);
break;
+ case KEY_DISABLE_SIZE_COMPAT_MODE_AFTER_ORIENTATION_CHANGE_FROM_APP:
+ mIsSizeCompatModeDisabledAfterOrientationChangeFromApp =
+ getDeviceConfig(key, defaultValue);
+ break;
default:
throw new AssertionError("Unexpected flag name: " + key);
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index c20a51338739..d9f2b6e4a0a3 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1433,7 +1433,7 @@ final class LetterboxUiController {
* the first opaque activity beneath.
*/
boolean hasInheritedLetterboxBehavior() {
- return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+ return mLetterboxConfigListener != null;
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 9e0e8c477573..7ff92afc9d29 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1456,7 +1456,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
*/
@Configuration.Orientation
int getRequestedConfigurationOrientation(boolean forDisplay) {
- int requestedOrientation = getOverrideOrientation();
+ return getRequestedConfigurationOrientation(forDisplay, getOverrideOrientation());
+ }
+
+ /**
+ * Gets the configuration orientation by the requested screen orientation
+ *
+ * @param forDisplay whether it is the requested config orientation for display.
+ * If {@code true}, we may reverse the requested orientation if the root is
+ * different from the display, so that when the display rotates to the
+ * reversed orientation, the requested app will be in the requested
+ * orientation.
+ * @param requestedOrientation the screen orientation({@link ScreenOrientation}) that is
+ * requested
+ * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE},
+ * {@link Configuration#ORIENTATION_PORTRAIT},
+ * {@link Configuration#ORIENTATION_UNDEFINED}).
+ */
+ @Configuration.Orientation
+ int getRequestedConfigurationOrientation(boolean forDisplay,
+ @ScreenOrientation int requestedOrientation) {
final RootDisplayArea root = getRootDisplayArea();
if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) {
// Reverse the requested orientation if the orientation of its root is different from
@@ -1466,7 +1485,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// (portrait).
// When an app below the DAG is requesting landscape, it should actually request the
// display to be portrait, so that the DAG and the app will be in landscape.
- requestedOrientation = reverseOrientation(getOverrideOrientation());
+ requestedOrientation = reverseOrientation(requestedOrientation);
}
if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8931d8030df2..e6c1e75da581 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6198,9 +6198,10 @@ public class WindowManagerService extends IWindowManager.Stub
waitingForConfig = waitingForRemoteDisplayChange = false;
numOpeningApps = 0;
}
- if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
- || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
- || mClientFreezingScreen || numOpeningApps > 0) {
+ final boolean waitingForApps = mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT
+ && (mAppsFreezingScreen > 0 || numOpeningApps > 0);
+ if (waitingForConfig || waitingForRemoteDisplayChange || waitingForApps
+ || mClientFreezingScreen) {
ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
+ "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
+ "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 52f2b6351265..41e0fd715889 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4577,7 +4577,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
void requestUpdateWallpaperIfNeeded() {
final DisplayContent dc = getDisplayContent();
- if (dc != null && hasWallpaper()) {
+ if (dc != null && ((mIsWallpaper && !mLastConfigReportedToClient) || hasWallpaper())) {
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
dc.setLayoutNeeded();
mWmService.mWindowPlacerLocked.requestTraversal();
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 0993295e162f..2505abf2d160 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -152,6 +152,8 @@ class ContactsQueryHelper {
}
} catch (SQLiteException exception) {
Slog.w("SQLite exception when querying contacts.", exception);
+ } catch (IllegalArgumentException exception) {
+ Slog.w("Illegal Argument exception when querying contacts.", exception);
}
if (found && lookupKey != null && hasPhoneNumber) {
return queryPhoneNumber(lookupKey);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index eff9e8da9a76..872734f7a01d 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -129,7 +129,6 @@ public class DataManager {
private final List<PeopleService.ConversationsListener> mConversationsListeners =
new ArrayList<>(1);
private final Handler mHandler;
-
private ContentObserver mCallLogContentObserver;
private ContentObserver mMmsSmsContentObserver;
@@ -1106,6 +1105,7 @@ public class DataManager {
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
mInjector.getBackgroundExecutor().execute(() -> {
PackageData packageData = getPackage(packageName, user.getIdentifier());
+ boolean hasCachedShortcut = false;
for (ShortcutInfo shortcut : shortcuts) {
if (ShortcutHelper.isConversationShortcut(
shortcut, mShortcutServiceInternal, user.getIdentifier())) {
@@ -1114,15 +1114,18 @@ public class DataManager {
? packageData.getConversationInfo(shortcut.getId()) : null;
if (conversationInfo == null
|| !conversationInfo.isShortcutCachedForNotification()) {
- // This is a newly cached shortcut. Clean up the existing cached
- // shortcuts to ensure the cache size is under the limit.
- cleanupCachedShortcuts(user.getIdentifier(),
- MAX_CACHED_RECENT_SHORTCUTS - 1);
+ hasCachedShortcut = true;
}
}
addOrUpdateConversationInfo(shortcut);
}
}
+ // Added at least one new conversation. Uncache older existing cached
+ // shortcuts to ensure the cache size is under the limit.
+ if (hasCachedShortcut) {
+ cleanupCachedShortcuts(user.getIdentifier(),
+ MAX_CACHED_RECENT_SHORTCUTS);
+ }
});
}
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 35a677e0f816..817b245a78bf 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -377,6 +377,33 @@ public class PersistentDataStoreTest {
assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
}
+ @Test
+ public void testStoreAndRestoreBrightnessNitsForDefaultDisplay() {
+ float brightnessNitsForDefaultDisplay = 190;
+ mDataStore.loadIfNeeded();
+ mDataStore.setBrightnessNitsForDefaultDisplay(brightnessNitsForDefaultDisplay);
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ mInjector.setWriteStream(baos);
+ mDataStore.saveIfNeeded();
+ mTestLooper.dispatchAll();
+ assertTrue(mInjector.wasWriteSuccessful());
+ TestInjector newInjector = new TestInjector();
+ PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ newInjector.setReadStream(bais);
+ newDataStore.loadIfNeeded();
+ assertEquals(brightnessNitsForDefaultDisplay,
+ mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+ assertEquals(brightnessNitsForDefaultDisplay,
+ newDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+ }
+
+ @Test
+ public void testInitialBrightnessNitsForDefaultDisplay() {
+ mDataStore.loadIfNeeded();
+ assertEquals(-1, mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+ }
public class TestInjector extends PersistentDataStore.Injector {
private InputStream mReadStream;
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
new file mode 100644
index 000000000000..832bcd9d70e6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
@@ -0,0 +1 @@
+include /media/java/android/media/projection/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 299f15344dfa..16a02b678511 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -91,8 +91,16 @@ public final class ContactsQueryHelperTest {
}
@Test
- public void testQueryException_returnsFalse() {
- contentProvider.setThrowException(true);
+ public void testQuerySQLiteException_returnsFalse() {
+ contentProvider.setThrowSQLiteException(true);
+
+ Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+ assertFalse(mHelper.query(contactUri.toString()));
+ }
+
+ @Test
+ public void testQueryIllegalArgumentException_returnsFalse() {
+ contentProvider.setThrowIllegalArgumentException(true);
Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
assertFalse(mHelper.query(contactUri.toString()));
@@ -178,14 +186,18 @@ public final class ContactsQueryHelperTest {
private class ContactsContentProvider extends MockContentProvider {
private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
- private boolean throwException = false;
+ private boolean mThrowSQLiteException = false;
+ private boolean mThrowIllegalArgumentException = false;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
- if (throwException) {
+ if (mThrowSQLiteException) {
throw new SQLiteException();
}
+ if (mThrowIllegalArgumentException) {
+ throw new IllegalArgumentException();
+ }
for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
if (uri.isPathPrefixMatch(prefixUri)) {
@@ -195,8 +207,12 @@ public final class ContactsQueryHelperTest {
return mUriPrefixToCursorMap.get(uri);
}
- public void setThrowException(boolean throwException) {
- this.throwException = throwException;
+ public void setThrowSQLiteException(boolean throwException) {
+ this.mThrowSQLiteException = throwException;
+ }
+
+ public void setThrowIllegalArgumentException(boolean throwException) {
+ this.mThrowIllegalArgumentException = throwException;
}
private void registerCursor(Uri uriPrefix, Cursor cursor) {
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 96e2a0916bb1..99a361c03a2a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -18,6 +18,8 @@ package com.android.server.notification;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.NOT_FOREGROUND_SERVICE;
+import static android.app.ActivityManagerInternal.ServiceNotificationPolicy.SHOW_IMMEDIATELY;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.Notification.FLAG_AUTO_CANCEL;
import static android.app.Notification.FLAG_BUBBLE;
@@ -1160,6 +1162,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testEnqueuedBlockedNotifications_appBlockedChannelForegroundService()
throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
NotificationChannel channel = new NotificationChannel("blocked", "name",
NotificationManager.IMPORTANCE_NONE);
@@ -1182,6 +1186,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testEnqueuedBlockedNotifications_userBlockedChannelForegroundService()
throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
NotificationChannel channel =
new NotificationChannel("blockedbyuser", "name", IMPORTANCE_HIGH);
@@ -1261,6 +1267,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testEnqueuedBlockedNotifications_blockedAppForegroundService() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
when(mPermissionHelper.hasPermission(mUid)).thenReturn(false);
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
@@ -1590,6 +1598,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testEnqueueNotificationWithTag_FgsAddsFlags_dismissalAllowed() throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
@@ -1618,6 +1630,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testEnqueueNotificationWithTag_FGSaddsFlags_dismissalNotAllowed() throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
DeviceConfig.setProperty(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED,
@@ -1904,6 +1920,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelAllNotifications_IgnoreForegroundService() throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt())).thenReturn(SHOW_IMMEDIATELY);
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
mBinderService.enqueueNotificationWithTag(PKG, PKG,
@@ -1918,7 +1936,27 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testCancelAllNotifications_FgsFlag_NoFgs_Allowed() throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(NOT_FOREGROUND_SERVICE);
+ final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
+ sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCancelAllNotifications_IgnoreForegroundService",
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ mBinderService.cancelAllNotifications(PKG, sbn.getUserId());
+ waitForIdle();
+ StatusBarNotification[] notifs =
+ mBinderService.getActiveNotifications(sbn.getPackageName());
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
public void testCancelAllNotifications_IgnoreOtherPackages() throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final StatusBarNotification sbn = generateNotificationRecord(null).getSbn();
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
mBinderService.enqueueNotificationWithTag(PKG, PKG,
@@ -2006,6 +2044,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testRemoveForegroundServiceFlag_ImmediatelyAfterEnqueue() throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
Notification n =
new Notification.Builder(mContext, mTestNotificationChannel.getId())
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -2043,6 +2084,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationWithTag_fromApp_cannotCancelFgsChild()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
mService.isSystemUid = false;
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
@@ -2066,6 +2110,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationWithTag_fromApp_cannotCancelFgsParent()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
mService.isSystemUid = false;
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
@@ -2135,6 +2182,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelAllNotificationsFromApp_cannotCancelFgsChild()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
mService.isSystemUid = false;
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
@@ -2160,6 +2210,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelAllNotifications_fromApp_cannotCancelFgsParent()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
mService.isSystemUid = false;
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
@@ -2281,6 +2334,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationsFromListener_clearAll_GroupWithFgsParent()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
parent.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
@@ -2304,6 +2360,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationsFromListener_clearAll_GroupWithFgsChild()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
final NotificationRecord child = generateNotificationRecord(
@@ -2402,6 +2461,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationsFromListener_clearAll_Fgs()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final NotificationRecord child2 = generateNotificationRecord(
mTestNotificationChannel, 3, null, false);
child2.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
@@ -2466,6 +2528,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationsFromListener_byKey_GroupWithFgsParent()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
parent.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
@@ -2491,6 +2556,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationsFromListener_byKey_GroupWithFgsChild()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
final NotificationRecord child = generateNotificationRecord(
@@ -2596,6 +2664,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCancelNotificationsFromListener_byKey_Fgs()
throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final NotificationRecord child2 = generateNotificationRecord(
mTestNotificationChannel, 3, null, false);
child2.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
@@ -2762,6 +2833,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUserInitiatedCancelAllWithGroup_ForegroundServiceFlag() throws Exception {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
final NotificationRecord parent = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
final NotificationRecord child = generateNotificationRecord(
@@ -6199,6 +6273,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testRemoveForegroundServiceFlagFromNotification_enqueued() {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
Notification n = new Notification.Builder(mContext, "").build();
n.flags |= FLAG_FOREGROUND_SERVICE;
@@ -6218,6 +6295,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testRemoveForegroundServiceFlagFromNotification_posted() {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
Notification n = new Notification.Builder(mContext, "").build();
n.flags |= FLAG_FOREGROUND_SERVICE;
@@ -6240,6 +6320,68 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testCannotRemoveForegroundFlagWhenOverLimit_enqueued() {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+ Notification n = new Notification.Builder(mContext, "").build();
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ mService.addEnqueuedNotification(r);
+ }
+ Notification n = new Notification.Builder(mContext, "").build();
+ n.flags |= FLAG_FOREGROUND_SERVICE;
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG,
+ NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addEnqueuedNotification(r);
+
+ mInternalService.removeForegroundServiceFlagFromNotification(
+ PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+ waitForIdle();
+
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS,
+ mService.getNotificationRecordCount());
+ }
+
+ @Test
+ public void testCannotRemoveForegroundFlagWhenOverLimit_posted() {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
+ for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
+ Notification n = new Notification.Builder(mContext, "").build();
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, i, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ mService.addNotification(r);
+ }
+ Notification n = new Notification.Builder(mContext, "").build();
+ n.flags |= FLAG_FOREGROUND_SERVICE;
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG,
+ NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addNotification(r);
+
+ mInternalService.removeForegroundServiceFlagFromNotification(
+ PKG, r.getSbn().getId(), r.getSbn().getUserId());
+
+ waitForIdle();
+
+ assertEquals(NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS,
+ mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testAllowForegroundCustomToasts() throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
@@ -8230,7 +8372,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertNotNull(n.publicVersion.bigContentView);
assertNotNull(n.publicVersion.headsUpContentView);
- mService.fixNotification(n, PKG, "tag", 9, 0);
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
assertNull(n.contentView);
assertNull(n.bigContentView);
@@ -8921,6 +9063,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCanPostFgsWhenOverLimit() throws RemoteException {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
i, null, false).getSbn();
@@ -8946,6 +9091,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testCannotPostNonFgsWhenOverLimit() throws RemoteException {
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(SHOW_IMMEDIATELY);
for (int i = 0; i < NotificationManagerService.MAX_PACKAGE_NOTIFICATIONS; i++) {
StatusBarNotification sbn = generateNotificationRecord(mTestNotificationChannel,
i, null, false).getSbn();
@@ -8968,6 +9116,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
"testCanPostFgsWhenOverLimit - non fgs over limit!",
sbn2.getId(), sbn2.getNotification(), sbn2.getUserId());
+
+ when(mAmi.applyForegroundServiceNotification(
+ any(), anyString(), anyInt(), anyString(), anyInt()))
+ .thenReturn(NOT_FOREGROUND_SERVICE);
+ final StatusBarNotification sbn3 = generateNotificationRecord(mTestNotificationChannel,
+ 101, null, false).getSbn();
+ sbn3.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "testCanPostFgsWhenOverLimit - fake fgs over limit!",
+ sbn3.getId(), sbn3.getNotification(), sbn3.getUserId());
+
waitForIdle();
StatusBarNotification[] notifs =
@@ -10025,4 +10184,21 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mInternalService.sendReviewPermissionsNotification();
verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
}
+
+ @Test
+ public void fixNotification_withFgsFlag_butIsNotFgs() throws Exception {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+
+ Notification n = new Notification.Builder(mContext, "test")
+ .setFlag(FLAG_FOREGROUND_SERVICE, true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
+ .build();
+
+ mService.fixNotification(n, PKG, "tag", 9, 0, mUid, NOT_FOREGROUND_SERVICE);
+
+ assertFalse(n.isForegroundService());
+ assertFalse(n.hasColorizedPermission());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index ea50179746d8..0300eb06c220 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2377,7 +2377,7 @@ public class ActivityRecordTests extends WindowTestsBase {
.setScreenOrientation(SCREEN_ORIENTATION_BEHIND)
.build();
final int topOrientation = activityTop.getRequestedConfigurationOrientation();
- assertEquals(SCREEN_ORIENTATION_PORTRAIT, topOrientation);
+ assertEquals(ORIENTATION_PORTRAIT, topOrientation);
}
private void verifyProcessInfoUpdate(ActivityRecord activity, State state,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 117805bf344d..d77b6ada268e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -381,7 +381,7 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- public void testTranslucentActivitiesDontGoInSizeCompactMode() {
+ public void testTranslucentActivitiesDontGoInSizeCompatMode() {
mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true);
setUpDisplaySizeWithApp(2800, 1400);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
@@ -2454,11 +2454,11 @@ public class SizeCompatTests extends WindowTestsBase {
assertFalse(mActivity.inSizeCompatMode());
mActivity.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
-
- assertTrue(mActivity.inSizeCompatMode());
- // We should remember the original orientation.
+ // Activity is not in size compat mode because the orientation change request came from the
+ // app itself
+ assertFalse(mActivity.inSizeCompatMode());
assertEquals(mActivity.getResolvedOverrideConfiguration().orientation,
- Configuration.ORIENTATION_PORTRAIT);
+ Configuration.ORIENTATION_UNDEFINED);
}
@Test
@@ -3033,6 +3033,25 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testAppRequestsOrientationChange_notInSizeCompat() {
+ setUpDisplaySizeWithApp(2200, 1800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
+
+ // Activity is not in size compat mode because the orientation change request came from the
+ // app itself
+ assertFalse(mActivity.inSizeCompatMode());
+
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_270);
+ // Activity should go into size compat mode now because the orientation change came from the
+ // system (device rotation)
+ assertTrue(mActivity.inSizeCompatMode());
+ }
+
+ @Test
public void testLetterboxDetailsForStatusBar_noLetterbox() {
setUpDisplaySizeWithApp(2800, 1000);
addStatusBar(mActivity.mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 1407cdd8600c..65f31a0e15bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -52,6 +52,7 @@ import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
@@ -61,6 +62,7 @@ import android.view.RoundedCorners;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import android.window.ClientWindowFrames;
import androidx.test.filters.SmallTest;
@@ -338,6 +340,29 @@ public class WallpaperControllerTests extends WindowTestsBase {
}
@Test
+ public void testWallpaperReportConfigChange() {
+ final WindowState wallpaperWindow = createWallpaperWindow(mDisplayContent);
+ createWallpaperTargetWindow(mDisplayContent);
+ final WallpaperWindowToken wallpaperToken = wallpaperWindow.mToken.asWallpaperToken();
+ makeWindowVisible(wallpaperWindow);
+ wallpaperWindow.mLayoutSeq = mDisplayContent.mLayoutSeq;
+ // Assume the token was invisible and the latest config was reported.
+ wallpaperToken.commitVisibility(false);
+ wallpaperWindow.fillClientWindowFramesAndConfiguration(new ClientWindowFrames(),
+ new MergedConfiguration(), true /* useLatestConfig */, false /* relayoutVisible */);
+ assertTrue(wallpaperWindow.isLastConfigReportedToClient());
+
+ final Rect bounds = wallpaperToken.getBounds();
+ wallpaperToken.setBounds(new Rect(0, 0, bounds.width() / 2, bounds.height() / 2));
+ assertFalse(wallpaperWindow.isLastConfigReportedToClient());
+ // If there is a pending config change when changing to visible, it should tell the client
+ // to redraw by WindowState#reportResized.
+ wallpaperToken.commitVisibility(true);
+ waitUntilHandlersIdle();
+ assertTrue(wallpaperWindow.isLastConfigReportedToClient());
+ }
+
+ @Test
public void testWallpaperTokenVisibility() {
final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
final WindowState wallpaperWindow = createWallpaperWindow(dc);