summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp13
-rw-r--r--OWNERS3
-rw-r--r--StubLibraries.bp54
-rw-r--r--ZYGOTE_OWNERS1
-rw-r--r--apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt4
-rw-r--r--apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java33
-rw-r--r--apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java2
-rw-r--r--api/Android.bp2
-rw-r--r--api/api.go72
-rw-r--r--api/api_test.go68
-rw-r--r--core/api/Android.bp54
-rw-r--r--core/api/current.txt39
-rw-r--r--core/api/system-current.txt12
-rw-r--r--core/api/test-current.txt68
-rw-r--r--core/java/Android.bp22
-rw-r--r--core/java/android/app/Activity.java4
-rw-r--r--core/java/android/app/ActivityManager.java4
-rw-r--r--core/java/android/app/ActivityThread.java10
-rw-r--r--core/java/android/app/IApplicationThread.aidl1
-rw-r--r--core/java/android/app/Instrumentation.java2
-rw-r--r--core/java/android/app/LocaleConfig.java6
-rw-r--r--core/java/android/app/admin/DevicePolicyCache.java13
-rw-r--r--core/java/android/app/search/SearchTargetEvent.java14
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java147
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl12
-rw-r--r--core/java/android/companion/IOnMessageReceivedListener.aidl23
-rw-r--r--core/java/android/companion/IOnTransportsChangedListener.aidl25
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java11
-rw-r--r--core/java/android/companion/virtual/sensor/VirtualSensorConfig.java6
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/content/pm/ActivityInfo.java28
-rw-r--r--core/java/android/content/pm/ILauncherApps.aidl2
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java8
-rw-r--r--core/java/android/content/pm/LauncherActivityInfoInternal.java22
-rw-r--r--core/java/android/content/pm/LauncherApps.java37
-rw-r--r--core/java/android/content/pm/PackageInstaller.java7
-rw-r--r--core/java/android/credentials/CredentialManager.java110
-rw-r--r--core/java/android/credentials/CredentialProviderInfo.aidl (renamed from core/java/android/credentials/IListEnabledProvidersCallback.aidl)14
-rw-r--r--core/java/android/credentials/CredentialProviderInfo.java215
-rw-r--r--core/java/android/credentials/ICredentialManager.aidl9
-rw-r--r--core/java/android/hardware/display/DisplayManager.java5
-rw-r--r--core/java/android/hardware/display/VirtualDisplayConfig.java86
-rw-r--r--core/java/android/nfc/NfcAdapter.java78
-rw-r--r--core/java/android/provider/DocumentsProvider.java13
-rw-r--r--core/java/android/provider/Settings.java33
-rw-r--r--core/java/android/service/autofill/Dataset.java49
-rw-r--r--core/java/android/service/credentials/BeginCreateCredentialResponse.java12
-rw-r--r--core/java/android/service/credentials/BeginGetCredentialResponse.java12
-rw-r--r--core/java/android/service/credentials/CredentialEntry.java61
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfoFactory.java (renamed from core/java/android/service/credentials/CredentialProviderInfo.java)297
-rw-r--r--core/java/android/service/credentials/CredentialProviderService.java20
-rw-r--r--core/java/android/service/quicksettings/TileService.java2
-rw-r--r--core/java/android/service/voice/HotwordDetectionService.java16
-rw-r--r--core/java/android/service/voice/HotwordDetector.java6
-rw-r--r--core/java/android/service/voice/SandboxedDetectionInitializer.java (renamed from core/java/android/service/voice/SandboxedDetectionServiceBase.java)6
-rw-r--r--core/java/android/service/voice/VisualQueryDetectionService.java29
-rw-r--r--core/java/android/service/voice/VisualQueryDetector.java7
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java166
-rw-r--r--core/java/android/text/GraphemeClusterSegmentFinder.java71
-rw-r--r--core/java/android/util/FeatureFlagUtils.java6
-rw-r--r--core/java/android/view/DisplayEventReceiver.java42
-rw-r--r--core/java/android/view/InputWindowHandle.java4
-rw-r--r--core/java/android/view/MotionPredictor.java36
-rw-r--r--core/java/android/view/VelocityTracker.java7
-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/Window.java5
-rw-r--r--core/java/android/view/WindowManager.java45
-rw-r--r--core/java/android/view/autofill/AutofillManager.java8
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java4
-rw-r--r--core/java/android/window/WindowInfosListener.java9
-rw-r--r--core/java/android/window/WindowInfosListenerForTest.java136
-rw-r--r--core/java/com/android/internal/app/procstats/UidState.java1
-rw-r--r--core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java2
-rw-r--r--core/java/com/android/internal/infra/AbstractRemoteService.java7
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java35
-rw-r--r--core/java/com/android/internal/protolog/BaseProtoLogImpl.java12
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogImpl.java12
-rw-r--r--core/jni/android_view_DisplayEventReceiver.cpp84
-rw-r--r--core/jni/android_view_InputEventSender.cpp29
-rw-r--r--core/jni/android_view_MotionPredictor.cpp25
-rw-r--r--core/proto/android/providers/settings/system.proto3
-rw-r--r--core/proto/android/server/activitymanagerservice.proto2
-rw-r--r--core/res/Android.bp9
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--core/res/res/values/strings.xml3
-rw-r--r--core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java104
-rw-r--r--core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java82
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt39
-rw-r--r--core/tests/coretests/src/android/credentials/CredentialManagerTest.java107
-rw-r--r--core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java15
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--graphics/java/android/graphics/Gainmap.java35
-rw-r--r--graphics/java/android/graphics/text/GraphemeBreak.java59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java112
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java184
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java122
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java42
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java9
-rw-r--r--libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java111
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java39
-rw-r--r--libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java113
-rw-r--r--libs/hwui/Android.bp2
-rw-r--r--libs/hwui/Mesh.h29
-rw-r--r--libs/hwui/apex/LayoutlibLoader.cpp3
-rw-r--r--libs/hwui/apex/jni_runtime.cpp2
-rw-r--r--libs/hwui/jni/BufferUtils.cpp130
-rw-r--r--libs/hwui/jni/BufferUtils.h32
-rw-r--r--libs/hwui/jni/android_graphics_Mesh.cpp177
-rw-r--r--libs/hwui/jni/fonts/Font.cpp5
-rw-r--r--libs/hwui/jni/text/GraphemeBreak.cpp67
-rw-r--r--media/java/android/media/audiopolicy/AudioMix.java8
-rw-r--r--media/java/android/media/audiopolicy/AudioPolicyConfig.java4
-rw-r--r--media/java/android/media/projection/MediaProjection.java15
-rw-r--r--media/java/android/media/projection/OWNERS1
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl3
-rw-r--r--media/java/android/media/tv/ITvInputSession.aidl3
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java11
-rw-r--r--media/java/android/media/tv/TvInputManager.java18
-rw-r--r--media/java/android/media/tv/TvInputService.java17
-rw-r--r--media/java/android/media/tv/TvView.java15
-rw-r--r--media/java/android/media/tv/tuner/filter/Filter.java15
-rw-r--r--media/tests/AudioPolicyTest/Android.bp1
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java158
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml24
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java5
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java17
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java8
-rw-r--r--packages/CredentialManager/res/values/strings.xml9
-rw-r--r--packages/CredentialManager/res/values/themes.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt43
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt25
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt29
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt35
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt6
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt9
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt238
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt34
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt20
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt60
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt59
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt28
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java46
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt14
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt17
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt27
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java6
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java41
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java4
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java9
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/Android.bp9
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java41
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java10
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp43
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml32
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidTest.xml29
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/TEST_MAPPING15
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java183
-rw-r--r--packages/SystemUI/animation/.gitignore9
-rw-r--r--packages/SystemUI/animation/build.gradle37
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt8
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt77
-rw-r--r--packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt3
-rw-r--r--packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt151
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml2
-rw-r--r--packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_change.xml23
-rw-r--r--packages/SystemUI/res/drawable/ic_move_magnification.xml2
-rw-r--r--packages/SystemUI/res/drawable/qs_media_rec_scrim.xml25
-rw-r--r--packages/SystemUI/res/layout/media_recommendation_view.xml3
-rw-r--r--packages/SystemUI/res/layout/window_magnification_settings_view.xml4
-rw-r--r--packages/SystemUI/res/values/colors.xml4
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java15
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java30
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt31
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt (renamed from packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogContentViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModel.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shared/model/BacklightModel.kt (renamed from packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancesMetricsLogger.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java (renamed from packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt)23
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsEvent.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt118
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt140
-rw-r--r--packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java116
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt109
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt129
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt237
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt100
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt80
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt2
-rw-r--r--packages/SystemUI/unfold/Android.bp1
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt9
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt12
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt3
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt48
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java2
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java35
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java16
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java164
-rw-r--r--services/companion/java/com/android/server/companion/transport/Transport.java42
-rw-r--r--services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/SensorController.java3
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java3
-rw-r--r--services/core/java/com/android/server/LogMteState.java53
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java97
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java11
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java120
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java12
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerUtils.java23
-rw-r--r--services/core/java/com/android/server/am/AppExitInfoTracker.java2
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java1
-rw-r--r--services/core/java/com/android/server/am/BroadcastHistory.java41
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java48
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java17
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java17
-rw-r--r--services/core/java/com/android/server/am/SameProcessApplicationThread.java29
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java2
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java75
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java4
-rw-r--r--services/core/java/com/android/server/biometrics/log/OperationContextExt.java122
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java64
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java23
-rw-r--r--services/core/java/com/android/server/dreams/DreamShellCommand.java36
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java7
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssManagerService.java1
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java10
-rw-r--r--services/core/java/com/android/server/media/AudioAttributesUtils.java112
-rw-r--r--services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java240
-rw-r--r--services/core/java/com/android/server/media/BluetoothRouteController.java11
-rw-r--r--services/core/java/com/android/server/media/DeviceRouteController.java200
-rw-r--r--services/core/java/com/android/server/media/LegacyDeviceRouteController.java184
-rw-r--r--services/core/java/com/android/server/media/MediaFeatureFlagManager.java6
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java51
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java20
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java20
-rw-r--r--services/core/java/com/android/server/pm/Installer.java13
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java56
-rw-r--r--services/core/java/com/android/server/pm/OtaDexoptService.java23
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java36
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java31
-rw-r--r--services/core/java/com/android/server/pm/ResolveIntentHelper.java18
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java15
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java4
-rw-r--r--services/core/java/com/android/server/power/stats/CpuWakeupStats.java110
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java14
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java20
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java11
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java40
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java7
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java26
-rw-r--r--services/core/java/com/android/server/wm/Transition.java33
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java14
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java38
-rw-r--r--services/core/java/com/android/server/wm/WallpaperWindowToken.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java17
-rw-r--r--services/credentials/java/com/android/server/credentials/ClearRequestSession.java19
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java24
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java105
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java5
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java10
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java23
-rw-r--r--services/credentials/java/com/android/server/credentials/MetricUtilities.java47
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderClearSession.java4
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderCreateSession.java27
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java46
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderSession.java48
-rw-r--r--services/credentials/java/com/android/server/credentials/RemoteCredentialService.java31
-rw-r--r--services/credentials/java/com/android/server/credentials/RequestSession.java12
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java8
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java63
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java169
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java130
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java52
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java25
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java45
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java4
-rw-r--r--services/java/com/android/server/SystemServer.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java28
-rw-r--r--services/tests/servicestests/AndroidManifest.xml1
-rw-r--r--services/tests/servicestests/res/xml/irq_device_map_3.xml2
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java20
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java130
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt16
-rw-r--r--services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java247
-rw-r--r--services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java (renamed from services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java)12
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java144
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java98
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java54
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java76
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java10
-rw-r--r--services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java6
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java53
-rw-r--r--telephony/java/android/telephony/PreciseDisconnectCause.java17
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt3
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt51
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt7
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt3
-rw-r--r--tests/Input/src/com/android/test/input/MotionPredictorTest.kt10
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java13
-rw-r--r--tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java2
-rw-r--r--tests/MotionPrediction/src/test/motionprediction/DrawingView.kt4
-rw-r--r--tools/codegen/Android.bp2
-rw-r--r--tools/codegen/BUILD.bazel21
-rw-r--r--tools/codegen/manifest.txt1
526 files changed, 12519 insertions, 3776 deletions
diff --git a/Android.bp b/Android.bp
index effd7ce2a63a..d9615991520a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -580,7 +580,7 @@ metalava_framework_docs_args = "" +
"--hide Todo " +
"--hide Typo " +
"--hide UnavailableSymbol " +
- "--manifest $(location core/res/AndroidManifest.xml) "
+ "--manifest $(location :frameworks-base-core-AndroidManifest.xml) "
packages_to_document = [
"android",
@@ -617,7 +617,7 @@ stubs_defaults {
sdk_version: "none",
system_modules: "none",
java_version: "1.8",
- arg_files: ["core/res/AndroidManifest.xml"],
+ arg_files: [":frameworks-base-core-AndroidManifest.xml"],
aidl: {
local_include_dirs: [
"media/aidl",
@@ -696,12 +696,3 @@ build = [
"ProtoLibraries.bp",
"TestProtoLibraries.bp",
]
-
-java_api_contribution {
- name: "api-stubs-docs-non-updatable-public-stubs",
- api_surface: "public",
- api_file: "core/api/current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
diff --git a/OWNERS b/OWNERS
index 09a721f10c1b..dad8bfe3340c 100644
--- a/OWNERS
+++ b/OWNERS
@@ -30,8 +30,11 @@ per-file */TEST_MAPPING = *
# Support bulk translation updates
per-file */res*/values*/*.xml = byi@google.com, delphij@google.com
+per-file **.bp,**.mk = hansson@google.com
per-file *.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
per-file Android.mk = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
per-file framework-jarjar-rules.txt = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
+
+per-file ZYGOTE_OWNERS = file:/ZYGOTE_OWNERS
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 38413c22a3d5..b005591980c0 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -36,8 +36,8 @@ droidstubs {
args: metalava_framework_docs_args,
check_api: {
current: {
- api_file: "core/api/current.txt",
- removed_api_file: "core/api/removed.txt",
+ api_file: ":non-updatable-current.txt",
+ removed_api_file: ":non-updatable-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.public.latest",
@@ -88,8 +88,8 @@ droidstubs {
args: metalava_framework_docs_args + priv_apps,
check_api: {
current: {
- api_file: "core/api/system-current.txt",
- removed_api_file: "core/api/system-removed.txt",
+ api_file: ":non-updatable-system-current.txt",
+ removed_api_file: ":non-updatable-system-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.system.latest",
@@ -99,7 +99,7 @@ droidstubs {
api_lint: {
enabled: true,
new_since: ":android.api.system.latest",
- baseline_file: "core/api/system-lint-baseline.txt",
+ baseline_file: ":non-updatable-system-lint-baseline.txt",
},
},
dists: [
@@ -127,12 +127,12 @@ droidstubs {
args: metalava_framework_docs_args + test + priv_apps_in_stubs,
check_api: {
current: {
- api_file: "core/api/test-current.txt",
- removed_api_file: "core/api/test-removed.txt",
+ api_file: ":non-updatable-test-current.txt",
+ removed_api_file: ":non-updatable-test-removed.txt",
},
api_lint: {
enabled: true,
- baseline_file: "core/api/test-lint-baseline.txt",
+ baseline_file: ":non-updatable-test-lint-baseline.txt",
},
},
dists: [
@@ -172,8 +172,8 @@ droidstubs {
args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs,
check_api: {
current: {
- api_file: "core/api/module-lib-current.txt",
- removed_api_file: "core/api/module-lib-removed.txt",
+ api_file: ":non-updatable-module-lib-current.txt",
+ removed_api_file: ":non-updatable-module-lib-removed.txt",
},
last_released: {
api_file: ":android-non-updatable.api.module-lib.latest",
@@ -183,7 +183,7 @@ droidstubs {
api_lint: {
enabled: true,
new_since: ":android.api.module-lib.latest",
- baseline_file: "core/api/module-lib-lint-baseline.txt",
+ baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
},
},
dists: [
@@ -364,15 +364,15 @@ java_library {
java_library {
name: "android_system_server_stubs_current",
- defaults: ["android_stubs_dists_default"],
+ defaults: [
+ "android.jar_defaults",
+ "android_stubs_dists_default",
+ ],
srcs: [":services-non-updatable-stubs"],
installable: false,
static_libs: [
"android_module_lib_stubs_current",
],
- sdk_version: "none",
- system_modules: "none",
- java_version: "1.8",
dist: {
dir: "apistubs/android/system-server",
},
@@ -575,20 +575,7 @@ droidstubs {
droidstubs {
name: "hwbinder-stubs-docs",
- srcs: [
- "core/java/android/os/HidlSupport.java",
- "core/java/android/os/HidlMemory.java",
- "core/java/android/os/HwBinder.java",
- "core/java/android/os/HwBlob.java",
- "core/java/android/os/HwParcel.java",
- "core/java/android/os/IHwBinder.java",
- "core/java/android/os/IHwInterface.java",
- "core/java/android/os/DeadObjectException.java",
- "core/java/android/os/DeadSystemException.java",
- "core/java/android/os/NativeHandle.java",
- "core/java/android/os/RemoteException.java",
- "core/java/android/util/AndroidException.java",
- ],
+ srcs: [":hwbinder-stubs-srcs"],
libs: ["framework-annotations-lib"],
installable: false,
sdk_version: "core_platform",
@@ -610,12 +597,3 @@ java_library {
],
visibility: ["//visibility:public"],
}
-
-java_api_contribution {
- name: "frameworks-base-core-api-module-lib-stubs",
- api_surface: "module-lib",
- api_file: "core/api/module-lib-current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
diff --git a/ZYGOTE_OWNERS b/ZYGOTE_OWNERS
index 90a185b79b52..f6d15e03a892 100644
--- a/ZYGOTE_OWNERS
+++ b/ZYGOTE_OWNERS
@@ -1,4 +1,3 @@
-calin@google.com
chriswailes@google.com
maco@google.com
narayan@google.com
diff --git a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
index fabf8892b544..9482591c65b5 100644
--- a/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
+++ b/apct-tests/perftests/core/src/android/input/MotionPredictorBenchmark.kt
@@ -33,7 +33,6 @@ import androidx.test.filters.LargeTest
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.After
-import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -132,8 +131,7 @@ class MotionPredictorBenchmark {
predictor.record(moveEvent)
val predictionTime = eventTime + eventInterval
val predicted = predictor.predict(predictionTime.toNanos())
- assertEquals(1, predicted.size)
- assertTrue(predicted[0].eventTime <= (predictionTime + offset).toMillis())
+ assertTrue(predicted.eventTime <= (predictionTime + offset).toMillis())
}
}
diff --git a/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java b/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
index 2e44d82ca428..e9c6c1ae0c87 100644
--- a/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
+++ b/apct-tests/perftests/inputmethod/src/android/inputmethod/ImePerfTest.java
@@ -76,6 +76,7 @@ public class ImePerfTest extends ImePerfTestBase
implements ManualBenchmarkState.CustomizedIterationListener {
private static final String TAG = ImePerfTest.class.getSimpleName();
private static final long ANIMATION_NOT_STARTED = -1;
+ private static final int WAIT_PROCESS_KILL_TIMEOUT_MS = 2000;
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@@ -248,19 +249,18 @@ public class ImePerfTest extends ImePerfTestBase
boolean shouldRetry = false;
while (shouldRetry || state.keepRunning(measuredTimeNs)) {
shouldRetry = false;
- killBaselineIme();
+ killBaselineImeSync();
try (ImeSession imeSession = new ImeSession(BaselineIme.getName(
getInstrumentation().getContext()))) {
+ if (!mIsTraceStarted) {
+ startAsyncAtrace();
+ }
final AtomicReference<CountDownLatch> latchStart = new AtomicReference<>();
final Activity activity = getActivityWithFocus();
setImeListener(activity, latchStart, null /* latchEnd */);
latchStart.set(new CountDownLatch(1));
- if (!mIsTraceStarted) {
- startAsyncAtrace();
- }
-
final WindowInsetsController controller =
activity.getWindow().getDecorView().getWindowInsetsController();
AtomicLong startTime = new AtomicLong();
@@ -270,6 +270,7 @@ public class ImePerfTest extends ImePerfTestBase
});
measuredTimeNs = waitForAnimationStart(latchStart, startTime);
+ stopAsyncAtraceAndDumpTraces();
if (measuredTimeNs == ANIMATION_NOT_STARTED) {
// Animation didn't start within timeout,
@@ -285,7 +286,7 @@ public class ImePerfTest extends ImePerfTestBase
addResultToState(state);
}
- private void killBaselineIme() {
+ private void killBaselineImeSync() {
// pidof returns a space separated list of numeric PIDs.
String result = SystemUtil.runShellCommand(
"pidof com.android.perftests.inputmethod:BaselineIME");
@@ -294,7 +295,13 @@ public class ImePerfTest extends ImePerfTestBase
if (TextUtils.isEmpty(pid)) {
continue;
}
- Process.killProcess(Integer.parseInt(pid));
+ final int pidToKill = Integer.parseInt(pid);
+ Process.killProcess(pidToKill);
+ try {
+ // Wait kill IME process being settled down.
+ Process.waitForProcessDeath(pidToKill, WAIT_PROCESS_KILL_TIMEOUT_MS);
+ } catch (Exception e) {
+ }
}
}
@@ -381,7 +388,7 @@ public class ImePerfTest extends ImePerfTestBase
}
} finally {
if (mIsTraceStarted) {
- stopAsyncAtrace();
+ stopAsyncAtraceAndDumpTraces();
}
}
mActivityRule.finishActivity();
@@ -488,7 +495,7 @@ public class ImePerfTest extends ImePerfTestBase
startAsyncAtrace("wm view");
}
- private void stopAsyncAtrace() {
+ private void stopAsyncAtraceAndDumpTraces() {
if (!mIsTraceStarted) {
return;
}
@@ -504,6 +511,14 @@ public class ImePerfTest extends ImePerfTestBase
}
}
+ private void stopAsyncAtrace() {
+ if (!mIsTraceStarted) {
+ return;
+ }
+ mIsTraceStarted = false;
+ getUiAutomation().executeShellCommand("atrace --async_stop");
+ }
+
@Override
public void onStart(int iteration) {
// Do not capture trace when profiling because the result will be much slower.
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
index ca5913701d3b..804baf4e6504 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/WindowPerfTestBase.java
@@ -73,7 +73,7 @@ public class WindowPerfTestBase {
}
public static void startAsyncAtrace(String tags) {
- getUiAutomation().executeShellCommand("atrace -b 32768 --async_start " + tags);
+ getUiAutomation().executeShellCommand("atrace --async_start -b 32768 -c " + tags);
// Avoid atrace isn't ready immediately.
SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 79a26597ed24..5de117261085 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -5277,6 +5277,8 @@ public class DeviceIdleController extends SystemService
pw.print(" mScreenLocked="); pw.println(mScreenLocked);
pw.print(" mNetworkConnected="); pw.println(mNetworkConnected);
pw.print(" mCharging="); pw.println(mCharging);
+ pw.print(" activeEmergencyCall=");
+ pw.println(mEmergencyCallListener.isEmergencyCallActive());
if (mConstraints.size() != 0) {
pw.println(" mConstraints={");
for (int i = 0; i < mConstraints.size(); i++) {
diff --git a/api/Android.bp b/api/Android.bp
index 78ddc6ac2967..6e82f1c36eff 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -31,10 +31,12 @@ bootstrap_go_package {
"blueprint",
"soong",
"soong-android",
+ "soong-bp2build",
"soong-genrule",
"soong-java",
],
srcs: ["api.go"],
+ testSrcs: ["api_test.go"],
pluginFor: ["soong_build"],
}
diff --git a/api/api.go b/api/api.go
index 077ab9679ec9..25d97282035e 100644
--- a/api/api.go
+++ b/api/api.go
@@ -20,6 +20,7 @@ import (
"github.com/google/blueprint/proptools"
"android/soong/android"
+ "android/soong/bazel"
"android/soong/genrule"
"android/soong/java"
)
@@ -30,6 +31,7 @@ const i18n = "i18n.module.public.api"
const virtualization = "framework-virtualization"
var core_libraries_modules = []string{art, conscrypt, i18n}
+
// List of modules that are not yet updatable, and hence they can still compile
// against hidden APIs. These modules are filtered out when building the
// updatable-framework-module-impl (because updatable-framework-module-impl is
@@ -59,6 +61,7 @@ type CombinedApisProperties struct {
type CombinedApis struct {
android.ModuleBase
+ android.BazelModuleBase
properties CombinedApisProperties
}
@@ -99,6 +102,19 @@ type fgProps struct {
Visibility []string
}
+type Bazel_module struct {
+ Bp2build_available *bool
+}
+type bazelProperties struct {
+ *Bazel_module
+}
+
+var bp2buildNotAvailable = bazelProperties{
+ &Bazel_module{
+ Bp2build_available: proptools.BoolPtr(false),
+ },
+}
+
// Struct to pass parameters for the various merged [current|removed].txt file modules we create.
type MergedTxtDefinition struct {
// "current.txt" or "removed.txt"
@@ -144,7 +160,7 @@ func createMergedTxt(ctx android.LoadHookContext, txt MergedTxtDefinition) {
},
}
props.Visibility = []string{"//visibility:public"}
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+ ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
}
func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, system_server_modules []string) {
@@ -174,7 +190,7 @@ func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, sys
props := fgProps{}
props.Name = proptools.StringPtr(i.name)
props.Srcs = createSrcs(i.modules, i.tag)
- ctx.CreateModule(android.FileGroupFactory, &props)
+ ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
}
}
@@ -223,7 +239,7 @@ func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) {
props.Tools = []string{"api_versions_trimmer"}
props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)")
props.Dists = []android.Dist{{Targets: []string{"sdk"}}}
- ctx.CreateModule(genrule.GenRuleFactory, &props)
+ ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable)
}
}
@@ -315,7 +331,7 @@ func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []str
props.Name = proptools.StringPtr("all-modules-public-stubs-source")
props.Srcs = createSrcs(modules, "{.public.stubs.source}")
props.Visibility = []string{"//frameworks/base"}
- ctx.CreateModule(android.FileGroupFactory, &props)
+ ctx.CreateModule(android.FileGroupFactory, &props, &bp2buildNotAvailable)
}
func createMergedTxts(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) {
@@ -389,9 +405,57 @@ func combinedApisModuleFactory() android.Module {
module.AddProperties(&module.properties)
android.InitAndroidModule(module)
android.AddLoadHook(module, func(ctx android.LoadHookContext) { module.createInternalModules(ctx) })
+ android.InitBazelModule(module)
return module
}
+type bazelCombinedApisAttributes struct {
+ Scope bazel.StringAttribute
+ Base bazel.LabelAttribute
+ Deps bazel.LabelListAttribute
+}
+
+// combined_apis bp2build converter
+func (a *CombinedApis) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+ basePrefix := "non-updatable"
+ scopeNames := []string{"public", "system", "module-lib", "system-server"}
+ scopeToSuffix := map[string]string{
+ "public": "-current.txt",
+ "system": "-system-current.txt",
+ "module-lib": "-module-lib-current.txt",
+ "system-server": "-system-server-current.txt",
+ }
+
+ for _, scopeName := range scopeNames{
+ suffix := scopeToSuffix[scopeName]
+ name := a.Name() + suffix
+
+ var scope bazel.StringAttribute
+ scope.SetValue(scopeName)
+
+ var base bazel.LabelAttribute
+ base.SetValue(android.BazelLabelForModuleDepSingle(ctx, basePrefix+suffix))
+
+ var deps bazel.LabelListAttribute
+ classpath := a.properties.Bootclasspath
+ if scopeName == "system-server" {
+ classpath = a.properties.System_server_classpath
+ }
+ deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, classpath))
+
+ attrs := bazelCombinedApisAttributes{
+ Scope: scope,
+ Base: base,
+ Deps: deps,
+ }
+ props := bazel.BazelTargetModuleProperties{
+ Rule_class: "merged_txts",
+ Bzl_load_location: "//build/bazel/rules/java:merged_txts.bzl",
+ }
+ ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name}, &attrs)
+ }
+}
+
// Various utility methods below.
// Creates an array of ":<m><tag>" for each m in <modules>.
diff --git a/api/api_test.go b/api/api_test.go
new file mode 100644
index 000000000000..15b695ca0d36
--- /dev/null
+++ b/api/api_test.go
@@ -0,0 +1,68 @@
+// 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 api
+
+import (
+ "testing"
+
+ "android/soong/android"
+ "android/soong/bp2build"
+)
+
+func runCombinedApisTestCaseWithRegistrationCtxFunc(t *testing.T, tc bp2build.Bp2buildTestCase, registrationCtxFunc func(ctx android.RegistrationContext)) {
+ t.Helper()
+ (&tc).ModuleTypeUnderTest = "combined_apis"
+ (&tc).ModuleTypeUnderTestFactory = combinedApisModuleFactory
+ bp2build.RunBp2BuildTestCase(t, registrationCtxFunc, tc)
+}
+
+func runCombinedApisTestCase(t *testing.T, tc bp2build.Bp2buildTestCase) {
+ t.Helper()
+ runCombinedApisTestCaseWithRegistrationCtxFunc(t, tc, func(ctx android.RegistrationContext) {})
+}
+
+func TestCombinedApisGeneral(t *testing.T) {
+ runCombinedApisTestCase(t, bp2build.Bp2buildTestCase{
+ Description: "combined_apis, general case",
+ Blueprint: `combined_apis {
+ name: "foo",
+ bootclasspath: ["bcp"],
+ system_server_classpath: ["ssc"],
+}
+`,
+ ExpectedBazelTargets: []string{
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-current.txt", bp2build.AttrNameToString{
+ "scope": `"public"`,
+ "base": `":non-updatable-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-current.txt", bp2build.AttrNameToString{
+ "scope": `"system"`,
+ "base": `":non-updatable-system-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-module-lib-current.txt", bp2build.AttrNameToString{
+ "scope": `"module-lib"`,
+ "base": `":non-updatable-module-lib-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":bcp__BP2BUILD__MISSING__DEP"]`,
+ }),
+ bp2build.MakeBazelTargetNoRestrictions("merged_txts", "foo-system-server-current.txt", bp2build.AttrNameToString{
+ "scope": `"system-server"`,
+ "base": `":non-updatable-system-server-current.txt__BP2BUILD__MISSING__DEP"`,
+ "deps": `[":ssc__BP2BUILD__MISSING__DEP"]`,
+ }),
+ },
+ })
+}
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 114a957674ae..71a2ca2903f6 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -13,7 +13,10 @@
// limitations under the License.
package {
- default_visibility: ["//visibility:private"],
+ default_visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
@@ -27,31 +30,33 @@ package {
filegroup {
name: "non-updatable-current.txt",
srcs: ["current.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-removed.txt",
srcs: ["removed.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-system-current.txt",
srcs: ["system-current.txt"],
- visibility: ["//frameworks/base/api"],
}
filegroup {
name: "non-updatable-system-removed.txt",
srcs: ["system-removed.txt"],
- visibility: ["//frameworks/base/api"],
+}
+
+filegroup {
+ name: "non-updatable-system-lint-baseline.txt",
+ srcs: ["system-lint-baseline.txt"],
}
filegroup {
name: "non-updatable-module-lib-current.txt",
srcs: ["module-lib-current.txt"],
visibility: [
+ "//frameworks/base",
"//frameworks/base/api",
"//cts/tests/signature/api",
],
@@ -61,7 +66,46 @@ filegroup {
name: "non-updatable-module-lib-removed.txt",
srcs: ["module-lib-removed.txt"],
visibility: [
+ "//frameworks/base",
"//frameworks/base/api",
"//cts/tests/signature/api",
],
}
+
+filegroup {
+ name: "non-updatable-module-lib-lint-baseline.txt",
+ srcs: ["module-lib-lint-baseline.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-current.txt",
+ srcs: ["test-current.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-removed.txt",
+ srcs: ["test-removed.txt"],
+}
+
+filegroup {
+ name: "non-updatable-test-lint-baseline.txt",
+ srcs: ["test-lint-baseline.txt"],
+}
+
+java_api_contribution {
+ name: "api-stubs-docs-non-updatable-public-stubs",
+ api_surface: "public",
+ api_file: "current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
+
+java_api_contribution {
+ name: "frameworks-base-core-api-module-lib-stubs",
+ api_surface: "module-lib",
+ api_file: "module-lib-current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 2b1cb0e4ab65..211f60350cfa 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4492,8 +4492,8 @@ package android.app {
method public void openOptionsMenu();
method public void overrideActivityTransition(int, @AnimRes int, @AnimRes int);
method public void overrideActivityTransition(int, @AnimRes int, @AnimRes int, @ColorInt int);
- method public void overridePendingTransition(int, int);
- method public void overridePendingTransition(int, int, int);
+ method @Deprecated public void overridePendingTransition(int, int);
+ method @Deprecated public void overridePendingTransition(int, int, int);
method public void postponeEnterTransition();
method public void recreate();
method public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks);
@@ -10333,7 +10333,7 @@ package android.content {
field public static final int BIND_AUTO_CREATE = 1; // 0x1
field public static final int BIND_DEBUG_UNBIND = 2; // 0x2
field public static final int BIND_EXTERNAL_SERVICE = -2147483648; // 0x80000000
- field public static final long BIND_EXTERNAL_SERVICE_LONG = -9223372036854775808L; // 0x8000000000000000L
+ field public static final long BIND_EXTERNAL_SERVICE_LONG = 4611686018427387904L; // 0x4000000000000000L
field public static final int BIND_IMPORTANT = 64; // 0x40
field public static final int BIND_INCLUDE_CAPABILITIES = 4096; // 0x1000
field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
@@ -10441,7 +10441,6 @@ package android.content {
}
public static final class Context.BindServiceFlags {
- method public long getValue();
method @NonNull public static android.content.Context.BindServiceFlags of(long);
}
@@ -15485,14 +15484,14 @@ package android.graphics {
method @NonNull public float getMinDisplayRatioForHdrTransition();
method @NonNull public float[] getRatioMax();
method @NonNull public float[] getRatioMin();
- method @NonNull public void setDisplayRatioForFullHdr(float);
- method @NonNull public void setEpsilonHdr(float, float, float);
- method @NonNull public void setEpsilonSdr(float, float, float);
+ method public void setDisplayRatioForFullHdr(@FloatRange(from=1.0f) float);
+ method public void setEpsilonHdr(float, float, float);
+ method public void setEpsilonSdr(float, float, float);
method public void setGainmapContents(@NonNull android.graphics.Bitmap);
- method @NonNull public void setGamma(float, float, float);
- method @NonNull public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
- method @NonNull public void setRatioMax(float, float, float);
- method @NonNull public void setRatioMin(float, float, float);
+ method public void setGamma(float, float, float);
+ method public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float);
+ method public void setRatioMax(float, float, float);
+ method public void setRatioMin(float, float, float);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.graphics.Gainmap> CREATOR;
}
@@ -19593,7 +19592,7 @@ package android.hardware.display {
public final class VirtualDisplayConfig implements android.os.Parcelable {
method public int describeContents();
method public int getDensityDpi();
- method @NonNull public java.util.List<java.lang.String> getDisplayCategories();
+ method @NonNull public java.util.Set<java.lang.String> getDisplayCategories();
method public int getFlags();
method public int getHeight();
method @NonNull public String getName();
@@ -19608,7 +19607,7 @@ package android.hardware.display {
ctor public VirtualDisplayConfig.Builder(@NonNull String, @IntRange(from=1) int, @IntRange(from=1) int, @IntRange(from=1) int);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder addDisplayCategory(@NonNull String);
method @NonNull public android.hardware.display.VirtualDisplayConfig build();
- method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCategories(@NonNull java.util.Set<java.lang.String>);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setFlags(int);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setRequestedRefreshRate(@FloatRange(from=0.0f) float);
method @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setSurface(@Nullable android.view.Surface);
@@ -27309,6 +27308,7 @@ package android.media.tv {
field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2
field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0
field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1
+ field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id";
field public static final String TV_MESSAGE_TYPE_CLOSED_CAPTION = "CC";
field public static final String TV_MESSAGE_TYPE_WATERMARK = "Watermark";
field public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4; // 0x4
@@ -27437,6 +27437,7 @@ package android.media.tv {
method public boolean onTrackballEvent(android.view.MotionEvent);
method public abstract boolean onTune(android.net.Uri);
method public boolean onTune(android.net.Uri, android.os.Bundle);
+ method public void onTvMessage(@NonNull String, @NonNull android.os.Bundle);
method public void onUnblockContent(android.media.tv.TvContentRating);
method public void setOverlayViewEnabled(boolean);
}
@@ -36890,6 +36891,7 @@ package android.provider {
method public String[] getDocumentStreamTypes(String, String);
method public String getDocumentType(String) throws java.io.FileNotFoundException;
method public final String getType(android.net.Uri);
+ method @Nullable public final String getTypeAnonymous(@NonNull android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public boolean isChildDocument(String, String);
method public String moveDocument(String, String, String) throws java.io.FileNotFoundException;
@@ -39766,6 +39768,7 @@ package android.service.autofill {
method @NonNull public android.service.autofill.Dataset.Builder setAuthentication(@Nullable android.content.IntentSender);
method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull android.view.autofill.AutofillId, @Nullable android.service.autofill.Field);
method @NonNull public android.service.autofill.Dataset.Builder setField(@NonNull String, @NonNull android.service.autofill.Field);
+ method @NonNull public android.service.autofill.Dataset.Builder setFieldForAllHints(@NonNull android.service.autofill.Field);
method @NonNull public android.service.autofill.Dataset.Builder setId(@Nullable String);
method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation);
method @Deprecated @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.service.autofill.InlinePresentation, @NonNull android.service.autofill.InlinePresentation);
@@ -40549,7 +40552,7 @@ package android.service.credentials {
method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder addCreateEntry(@NonNull android.service.credentials.CreateEntry);
method @NonNull public android.service.credentials.BeginCreateCredentialResponse build();
method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setCreateEntries(@NonNull java.util.List<android.service.credentials.CreateEntry>);
- method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
+ method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.RemoteEntry);
}
public class BeginGetCredentialOption implements android.os.Parcelable {
@@ -40598,7 +40601,7 @@ package android.service.credentials {
method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setActions(@NonNull java.util.List<android.service.credentials.Action>);
method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setAuthenticationActions(@NonNull java.util.List<android.service.credentials.Action>);
method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setCredentialEntries(@NonNull java.util.List<android.service.credentials.CredentialEntry>);
- method @NonNull public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
+ method @NonNull @RequiresPermission("android.permission.PROVIDE_REMOTE_CREDENTIALS") public android.service.credentials.BeginGetCredentialResponse.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.RemoteEntry);
}
public final class CallingAppInfo implements android.os.Parcelable {
@@ -40640,9 +40643,11 @@ package android.service.credentials {
}
public class CredentialEntry implements android.os.Parcelable {
+ ctor public CredentialEntry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
ctor public CredentialEntry(@NonNull android.service.credentials.BeginGetCredentialOption, @NonNull android.app.slice.Slice);
+ ctor public CredentialEntry(@NonNull String, @NonNull android.app.slice.Slice);
method public int describeContents();
- method @NonNull public android.service.credentials.BeginGetCredentialOption getBeginGetCredentialOption();
+ method @NonNull public String getBeginGetCredentialOptionId();
method @NonNull public android.app.slice.Slice getSlice();
method @NonNull public String getType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -51443,7 +51448,7 @@ package android.view {
public final class MotionPredictor {
ctor public MotionPredictor(@NonNull android.content.Context);
method public boolean isPredictionAvailable(int, int);
- method @NonNull public java.util.List<android.view.MotionEvent> predict(long);
+ method @Nullable public android.view.MotionEvent predict(long);
method public void record(@NonNull android.view.MotionEvent);
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8a6faa109522..11a0f0b81ddd 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -254,7 +254,7 @@ package android {
field public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
field public static final String POWER_SAVER = "android.permission.POWER_SAVER";
field public static final String PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE = "android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE";
- field public static final String PROVIDE_HYBRID_CREDENTIAL_SERVICE = "android.permission.PROVIDE_HYBRID_CREDENTIAL_SERVICE";
+ field public static final String PROVIDE_REMOTE_CREDENTIALS = "android.permission.PROVIDE_REMOTE_CREDENTIALS";
field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
field public static final String PROVISION_DEMO_DEVICE = "android.permission.PROVISION_DEMO_DEVICE";
@@ -2363,6 +2363,8 @@ package android.app.search {
method @NonNull public String getTargetId();
method @NonNull public java.util.List<java.lang.String> getTargetIds();
method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int ACTION_DELETE = 9; // 0x9
+ field public static final int ACTION_DISMISS = 10; // 0xa
field public static final int ACTION_DRAGNDROP = 7; // 0x7
field public static final int ACTION_LAUNCH_KEYBOARD_FOCUS = 6; // 0x6
field public static final int ACTION_LAUNCH_TOUCH = 5; // 0x5
@@ -3362,7 +3364,7 @@ package android.companion.virtual.sensor {
}
public static final class VirtualSensorConfig.Builder {
- ctor public VirtualSensorConfig.Builder(int, @NonNull String);
+ ctor public VirtualSensorConfig.Builder(@IntRange(from=1) int, @NonNull String);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setDirectChannelTypesSupported(int);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setHighestDirectReportRateLevel(int);
@@ -13166,7 +13168,7 @@ package android.service.voice {
method @NonNull public android.service.voice.HotwordDetectedResult.Builder setScore(int);
}
- public abstract class HotwordDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionServiceBase {
+ public abstract class HotwordDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionInitializer {
ctor public HotwordDetectionService();
method @Deprecated public static int getMaxCustomInitializationStatus();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
@@ -13238,7 +13240,7 @@ package android.service.voice {
method @NonNull public android.service.voice.HotwordRejectedResult.Builder setConfidenceLevel(int);
}
- public interface SandboxedDetectionServiceBase {
+ public interface SandboxedDetectionInitializer {
method public static int getMaxCustomInitializationStatus();
method public void onUpdateState(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, long, @Nullable java.util.function.IntConsumer);
field public static final int INITIALIZATION_STATUS_SUCCESS = 0; // 0x0
@@ -13260,7 +13262,7 @@ package android.service.voice {
field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.UnknownFailure> CREATOR;
}
- public abstract class VisualQueryDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionServiceBase {
+ public abstract class VisualQueryDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionInitializer {
ctor public VisualQueryDetectionService();
method public final void finishQuery() throws java.lang.IllegalStateException;
method public final void gainedAttention();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1860f433402c..a3bc3922126b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -899,6 +899,7 @@ package android.content.pm {
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_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN = 208648326L; // 0xc6fb886L
+ field public static final long OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS = 237531167L; // 0xe28701fL
field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
}
@@ -1071,6 +1072,40 @@ package android.content.rollback {
}
+package android.credentials {
+
+ public final class CredentialManager {
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.QUERY_ALL_PACKAGES, "android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS"}) public java.util.List<android.credentials.CredentialProviderInfo> getCredentialProviderServicesForTesting(int);
+ method public static boolean isServiceEnabled(@NonNull android.content.Context);
+ field public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0; // 0x0
+ field public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1; // 0x1
+ field public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2; // 0x2
+ }
+
+ public final class CredentialProviderInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getCapabilities();
+ method @NonNull public android.content.ComponentName getComponentName();
+ method @Nullable public CharSequence getLabel(@NonNull android.content.Context);
+ method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context);
+ method @NonNull public android.content.pm.ServiceInfo getServiceInfo();
+ method @NonNull public boolean hasCapability(@NonNull String);
+ method public boolean isEnabled();
+ method public boolean isSystemProvider();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialProviderInfo> CREATOR;
+ }
+
+ public static final class CredentialProviderInfo.Builder {
+ ctor public CredentialProviderInfo.Builder(@NonNull android.content.pm.ServiceInfo);
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder addCapabilities(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.credentials.CredentialProviderInfo build();
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder setEnabled(boolean);
+ method @NonNull public android.credentials.CredentialProviderInfo.Builder setSystemProvider(boolean);
+ }
+
+}
+
package android.credentials.ui {
public final class AuthenticationEntry implements android.os.Parcelable {
@@ -1921,6 +1956,10 @@ package android.media.tv {
method public void removeHardwareDevice(int);
}
+ public class TvView extends android.view.ViewGroup {
+ method public void notifyTvMessage(@NonNull String, @NonNull android.os.Bundle);
+ }
+
}
package android.media.tv.tuner {
@@ -2629,8 +2668,13 @@ package android.service.autofill {
method @Nullable public android.content.IntentSender getAuthentication();
method @Nullable public java.util.ArrayList<java.lang.String> getAutofillDatatypes();
method @Nullable public android.content.ClipData getFieldContent();
+ method @Nullable public android.widget.RemoteViews getFieldDialogPresentation(int);
method @Nullable public java.util.ArrayList<android.view.autofill.AutofillId> getFieldIds();
+ method @Nullable public android.service.autofill.InlinePresentation getFieldInlinePresentation(int);
+ method @Nullable public android.service.autofill.InlinePresentation getFieldInlineTooltipPresentation(int);
+ method @Nullable public android.widget.RemoteViews getFieldPresentation(int);
method @Nullable public java.util.ArrayList<android.view.autofill.AutofillValue> getFieldValues();
+ method @Nullable public android.service.autofill.Dataset.DatasetFieldFilter getFilter(int);
method @Nullable public String getId();
method public boolean isEmpty();
}
@@ -2639,6 +2683,13 @@ package android.service.autofill {
method @NonNull public android.service.autofill.Dataset.Builder setContent(@NonNull android.view.autofill.AutofillId, @Nullable android.content.ClipData);
}
+ public static final class Dataset.DatasetFieldFilter implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public java.util.regex.Pattern getPattern();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.Dataset.DatasetFieldFilter> CREATOR;
+ }
+
public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
method public void apply(@NonNull android.service.autofill.ValueFinder, @NonNull android.widget.RemoteViews, int) throws java.lang.Exception;
}
@@ -2812,7 +2863,7 @@ package android.service.voice {
method @NonNull public android.service.voice.AlwaysOnHotwordDetector.EventPayload.Builder setKeyphraseRecognitionExtras(@NonNull java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
}
- public abstract class HotwordDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionServiceBase {
+ public abstract class HotwordDetectionService extends android.app.Service implements android.service.voice.SandboxedDetectionInitializer {
field public static final boolean ENABLE_PROXIMITY_RESULT = true;
}
@@ -3291,7 +3342,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);
@@ -3419,6 +3472,7 @@ package android.view.autofill {
}
public final class AutofillManager {
+ field public static final String ANY_HINT = "any";
field public static final int FLAG_SMART_SUGGESTION_OFF = 0; // 0x0
field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
@@ -3923,6 +3977,18 @@ package android.window {
method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction);
}
+ public class WindowInfosListenerForTest {
+ ctor public WindowInfosListenerForTest();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void addWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>);
+ method public void removeWindowInfosListener(@NonNull java.util.function.Consumer<java.util.List<android.window.WindowInfosListenerForTest.WindowInfo>>);
+ }
+
+ public static class WindowInfosListenerForTest.WindowInfo {
+ field @NonNull public final android.graphics.Rect bounds;
+ field @NonNull public final String name;
+ field @NonNull public final android.os.IBinder windowToken;
+ }
+
public class WindowOrganizer {
ctor public WindowOrganizer();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 738e2de7d44b..39695770ba58 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -285,6 +285,28 @@ filegroup {
],
}
+filegroup {
+ name: "hwbinder-stubs-srcs",
+ srcs: [
+ "android/os/HidlSupport.java",
+ "android/os/HidlMemory.java",
+ "android/os/HwBinder.java",
+ "android/os/HwBlob.java",
+ "android/os/HwParcel.java",
+ "android/os/IHwBinder.java",
+ "android/os/IHwInterface.java",
+ "android/os/DeadObjectException.java",
+ "android/os/DeadSystemException.java",
+ "android/os/NativeHandle.java",
+ "android/os/RemoteException.java",
+ "android/util/AndroidException.java",
+ ],
+ visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
+}
+
cc_defaults {
name: "incremental_default",
cflags: [
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d9c7a27cb95e..125e7270b0e2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6621,7 +6621,9 @@ public class Activity extends ContextThemeWrapper
* the incoming activity. Use 0 for no animation.
* @param exitAnim A resource ID of the animation resource to use for
* the outgoing activity. Use 0 for no animation.
+ * @deprecated Use {@link #overrideActivityTransition(int, int, int)}} instead.
*/
+ @Deprecated
public void overridePendingTransition(int enterAnim, int exitAnim) {
overridePendingTransition(enterAnim, exitAnim, 0);
}
@@ -6644,7 +6646,9 @@ public class Activity extends ContextThemeWrapper
* the outgoing activity. Use 0 for no animation.
* @param backgroundColor The background color to use for the background during the animation if
* the animation requires a background. Set to 0 to not override the default color.
+ * @deprecated Use {@link #overrideActivityTransition(int, int, int, int)}} instead.
*/
+ @Deprecated
public void overridePendingTransition(int enterAnim, int exitAnim, int backgroundColor) {
ActivityClient.getInstance().overridePendingTransition(mToken, getPackageName(), enterAnim,
exitAnim, backgroundColor);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 87fe215edd85..b4068dbf0797 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4208,8 +4208,8 @@ public class ActivityManager {
* processes to reclaim memory; the system will take care of restarting
* these processes in the future as needed.
*
- * <p class="note">On devices with a {@link Build.VERSION#SECURITY_PATCH} of 2022-12-01 or
- * greater, third party applications can only use this API to kill their own processes.
+ * <p class="note">On devices that run Android 14 or higher,
+ * third party applications can only use this API to kill their own processes.
* </p>
*
* @param packageName The name of the package whose processes are to
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index dfdfd0e2054e..a3ada763265a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1154,6 +1154,11 @@ public final class ActivityThread extends ClientTransactionHandler
}
@Override
+ public final void schedulePing(RemoteCallback pong) {
+ sendMessage(H.PING, pong);
+ }
+
+ @Override
public final void bindApplication(String processName, ApplicationInfo appInfo,
String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage,
ProviderInfoList providerList, ComponentName instrumentationName,
@@ -2154,6 +2159,7 @@ public final class ActivityThread extends ClientTransactionHandler
public static final int DUMP_GFXINFO = 165;
public static final int DUMP_RESOURCES = 166;
public static final int TIMEOUT_SERVICE = 167;
+ public static final int PING = 168;
public static final int INSTRUMENT_WITHOUT_RESTART = 170;
public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
@@ -2209,6 +2215,7 @@ public final class ActivityThread extends ClientTransactionHandler
return "FINISH_INSTRUMENTATION_WITHOUT_RESTART";
case DUMP_RESOURCES: return "DUMP_RESOURCES";
case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
+ case PING: return "PING";
}
}
return Integer.toString(code);
@@ -2292,6 +2299,9 @@ public final class ActivityThread extends ClientTransactionHandler
handleTimeoutService((IBinder) msg.obj, msg.arg1);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
+ case PING:
+ ((RemoteCallback) msg.obj).sendResult(null);
+ break;
case CONFIGURATION_CHANGED:
mConfigurationController.handleConfigurationChanged((Configuration) msg.obj);
break;
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index dad9b435e9e7..4f77203c8c6f 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -172,4 +172,5 @@ oneway interface IApplicationThread {
in TranslationSpec targetSpec, in List<AutofillId> viewIds,
in UiTranslationSpec uiTranslationSpec);
void scheduleTimeoutService(IBinder token, int startId);
+ void schedulePing(in RemoteCallback pong);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 5cad1aeb9fe7..ac928116481f 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -93,7 +93,7 @@ public class Instrumentation {
private static final String TAG = "Instrumentation";
- private static final long CONNECT_TIMEOUT_MILLIS = 5000;
+ private static final long CONNECT_TIMEOUT_MILLIS = 60_000;
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index f4cd60d08804..97cc706fbab6 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -58,10 +58,10 @@ import java.util.Set;
*
* @attr ref android.R.styleable#LocaleConfig_Locale_name
* @attr ref android.R.styleable#AndroidManifestApplication_localeConfig
- *
- * <p>For more information about the LocaleConfig overridden by the application, see
- * TODO(b/261528306): add link to guide
*/
+// Add following to last Note: when guide is written:
+// For more information about the LocaleConfig overridden by the application, see TODO(b/261528306):
+// add link to guide
public class LocaleConfig implements Parcelable {
private static final String TAG = "LocaleConfig";
public static final String TAG_LOCALE_CONFIG = "locale-config";
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index 3957732c56dd..b6e83c8bc8a1 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -19,6 +19,9 @@ import android.annotation.UserIdInt;
import com.android.server.LocalServices;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Stores a copy of the set of device policies maintained by {@link DevicePolicyManager} that
* can be accessed from any place without risking dead locks.
@@ -61,6 +64,12 @@ public abstract class DevicePolicyCache {
public abstract boolean canAdminGrantSensorsPermissions();
/**
+ * Returns a list of package names for which all launcher shortcuts should be modified to be
+ * launched in the managed profile and badged accordingly.
+ */
+ public abstract List<String> getLauncherShortcutOverrides();
+
+ /**
* Empty implementation.
*/
private static class EmptyDevicePolicyCache extends DevicePolicyCache {
@@ -85,5 +94,9 @@ public abstract class DevicePolicyCache {
public boolean canAdminGrantSensorsPermissions() {
return false;
}
+ @Override
+ public List<String> getLauncherShortcutOverrides() {
+ return new ArrayList<>();
+ }
}
}
diff --git a/core/java/android/app/search/SearchTargetEvent.java b/core/java/android/app/search/SearchTargetEvent.java
index d4915afd5ce6..e8ef9227ad05 100644
--- a/core/java/android/app/search/SearchTargetEvent.java
+++ b/core/java/android/app/search/SearchTargetEvent.java
@@ -50,7 +50,9 @@ public final class SearchTargetEvent implements Parcelable {
ACTION_LAUNCH_TOUCH,
ACTION_LAUNCH_KEYBOARD_FOCUS,
ACTION_DRAGNDROP,
- ACTION_SURFACE_INVISIBLE
+ ACTION_SURFACE_INVISIBLE,
+ ACTION_DELETE,
+ ACTION_DISMISS
})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionType {}
@@ -114,6 +116,16 @@ public final class SearchTargetEvent implements Parcelable {
*/
public static final int ACTION_SURFACE_INVISIBLE = 8;
+ /**
+ * Constant that defines user deleted a target.
+ */
+ public static final int ACTION_DELETE = 9;
+
+ /**
+ * Constant that defines user dismissed a target.
+ */
+ public static final int ACTION_DISMISS = 10;
+
private SearchTargetEvent(@NonNull List<String> targetIds,
@Nullable String location,
@ActionType int actionType,
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index de4f619392c1..a522cc04eff1 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -801,6 +801,119 @@ public final class CompanionDeviceManager {
}
/**
+ * Listener for any changes to {@link com.android.server.companion.transport.Transport}.
+ *
+ * @hide
+ */
+ public interface OnTransportsChangedListener {
+ /**
+ * Invoked when a change occurs to any of the transports
+ *
+ * @param associations all the associations which have connected transports
+ */
+ void onTransportsChanged(@NonNull List<AssociationInfo> associations);
+ }
+
+ /**
+ * Register a listener for any changes to
+ * {@link com.android.server.companion.transport.Transport}. Your app will receive a callback to
+ * {@link OnTransportsChangedListener} immediately with all the existing transports.
+ *
+ * @hide
+ */
+ public void addOnTransportsChangedListener(
+ @NonNull Executor executor, @NonNull OnTransportsChangedListener listener) {
+ final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
+ executor, listener);
+ try {
+ mService.addOnTransportsChangedListener(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister a listener to stop receiving any changes to
+ * {@link com.android.server.companion.transport.Transport}.
+ *
+ * @hide
+ */
+ public void removeOnTransportsChangedListener(
+ @NonNull OnTransportsChangedListener listener) {
+ final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
+ null, listener);
+ try {
+ mService.removeOnTransportsChangedListener(proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Send a message to remote devices
+ *
+ * @hide
+ */
+ public void sendMessage(int messageType, byte[] data, int[] associationIds) {
+ try {
+ mService.sendMessage(messageType, data, associationIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Listener when a message is received for the registered message type
+ *
+ * @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
+ *
+ * @hide
+ */
+ public interface OnMessageReceivedListener {
+ /**
+ * Called when a message is received
+ */
+ void onMessageReceived(int associationId, byte[] data);
+ }
+
+ /**
+ * Register a listener to receive callbacks when a message is received by the given type
+ *
+ * @see com.android.server.companion.transport.Transport for supported message types
+ *
+ * @hide
+ */
+ public void addOnMessageReceivedListener(@NonNull Executor executor, int messageType,
+ OnMessageReceivedListener listener) {
+ final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
+ executor, listener);
+ try {
+ mService.addOnMessageReceivedListener(messageType, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister a listener to stop receiving callbacks when a message is received by the given
+ * type
+ *
+ * @see com.android.server.companion.transport.Transport for supported message types
+ *
+ * @hide
+ */
+ public void removeOnMessageReceivedListener(int messageType,
+ OnMessageReceivedListener listener) {
+ final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
+ null, listener);
+ try {
+ mService.removeOnMessageReceivedListener(messageType, proxy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether the bluetooth device represented by the mac address was recently associated
* with the companion app. This allows these devices to skip the Bluetooth pairing dialog if
* their pairing variant is {@link BluetoothDevice#PAIRING_VARIANT_CONSENT}.
@@ -1277,6 +1390,40 @@ public final class CompanionDeviceManager {
}
}
+ private static class OnTransportsChangedListenerProxy
+ extends IOnTransportsChangedListener.Stub {
+ private final Executor mExecutor;
+ private final OnTransportsChangedListener mListener;
+
+ private OnTransportsChangedListenerProxy(Executor executor,
+ OnTransportsChangedListener listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onTransportsChanged(@NonNull List<AssociationInfo> associations) {
+ mExecutor.execute(() -> mListener.onTransportsChanged(associations));
+ }
+ }
+
+ private static class OnMessageReceivedListenerProxy
+ extends IOnMessageReceivedListener.Stub {
+ private final Executor mExecutor;
+ private final OnMessageReceivedListener mListener;
+
+ private OnMessageReceivedListenerProxy(Executor executor,
+ OnMessageReceivedListener listener) {
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ @Override
+ public void onMessageReceived(int associationId, byte[] data) {
+ mExecutor.execute(() -> mListener.onMessageReceived(associationId, data));
+ }
+ }
+
private static class SystemDataTransferCallbackProxy extends ISystemDataTransferCallback.Stub {
private final Executor mExecutor;
private final OutcomeReceiver<Void, CompanionException> mCallback;
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index cb4baca73ba0..b5e2670e5299 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -19,6 +19,8 @@ package android.companion;
import android.app.PendingIntent;
import android.companion.IAssociationRequestCallback;
import android.companion.IOnAssociationsChangedListener;
+import android.companion.IOnMessageReceivedListener;
+import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
@@ -67,6 +69,16 @@ interface ICompanionDeviceManager {
void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
+ void addOnTransportsChangedListener(IOnTransportsChangedListener listener);
+
+ void removeOnTransportsChangedListener(IOnTransportsChangedListener listener);
+
+ void sendMessage(int messageType, in byte[] data, in int[] associationIds);
+
+ void addOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
+
+ void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
+
void notifyDeviceAppeared(int associationId);
void notifyDeviceDisappeared(int associationId);
diff --git a/core/java/android/companion/IOnMessageReceivedListener.aidl b/core/java/android/companion/IOnMessageReceivedListener.aidl
new file mode 100644
index 000000000000..17f03f80996e
--- /dev/null
+++ b/core/java/android/companion/IOnMessageReceivedListener.aidl
@@ -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 per missions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+/** @hide */
+interface IOnMessageReceivedListener {
+
+ oneway void onMessageReceived(int associationId, in byte[] data);
+} \ No newline at end of file
diff --git a/core/java/android/companion/IOnTransportsChangedListener.aidl b/core/java/android/companion/IOnTransportsChangedListener.aidl
new file mode 100644
index 000000000000..a10147627a9d
--- /dev/null
+++ b/core/java/android/companion/IOnTransportsChangedListener.aidl
@@ -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 per missions and
+ * limitations under the License.
+ */
+
+package android.companion;
+
+import android.companion.AssociationInfo;
+
+/** @hide */
+interface IOnTransportsChangedListener {
+
+ oneway void onTransportsChanged(in List<AssociationInfo> associations);
+} \ No newline at end of file
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 6cc4c8a24c48..90681cba7d83 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -545,12 +545,13 @@ public final class VirtualDeviceManager {
@VirtualDisplayFlag int flags,
@Nullable @CallbackExecutor Executor executor,
@Nullable VirtualDisplay.Callback callback) {
- VirtualDisplayConfig config = new VirtualDisplayConfig.Builder(
+ VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
getVirtualDisplayName(), width, height, densityDpi)
- .setSurface(surface)
- .setFlags(flags)
- .build();
- return createVirtualDisplay(config, executor, callback);
+ .setFlags(flags);
+ if (surface != null) {
+ builder.setSurface(surface);
+ }
+ return createVirtualDisplay(builder.build(), executor, callback);
}
/**
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index ffbdff8c2e3b..401e754abca6 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -17,6 +17,7 @@
package android.companion.virtual.sensor;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -167,7 +168,10 @@ public final class VirtualSensorConfig implements Parcelable {
* @param name The name of the sensor. Must be unique among all sensors with the same type
* that belong to the same virtual device.
*/
- public Builder(int type, @NonNull String name) {
+ public Builder(@IntRange(from = 1) int type, @NonNull String name) {
+ if (type <= 0) {
+ throw new IllegalArgumentException("Virtual sensor type must be positive");
+ }
mType = type;
mName = Objects.requireNonNull(name);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a412560d0347..36f7ff53eb76 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -316,10 +316,12 @@ public abstract class Context {
BIND_ALLOW_ACTIVITY_STARTS,
BIND_INCLUDE_CAPABILITIES,
BIND_SHARED_ISOLATED_PROCESS,
- // Intentionally not included, because it'd cause sign-extension.
+ // Intentionally not include BIND_EXTERNAL_SERVICE, because it'd cause sign-extension.
// This would allow Android Studio to show a warning, if someone tries to use
// BIND_EXTERNAL_SERVICE BindServiceFlags.
- BIND_EXTERNAL_SERVICE_LONG
+ BIND_EXTERNAL_SERVICE_LONG,
+ // Make sure no flag uses the sign bit (most significant bit) of the long integer,
+ // to avoid future confusion.
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlagsLongBits {}
@@ -338,6 +340,7 @@ public abstract class Context {
/**
* @return Return flags in 64 bits long integer.
+ * @hide
*/
public long getValue() {
return mValue;
@@ -678,13 +681,11 @@ public abstract class Context {
*/
public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
-
/**
* Works in the same way as {@link #BIND_EXTERNAL_SERVICE}, but it's defined as a (@code long)
* value that is compatible to {@link BindServiceFlags}.
*/
- public static final long BIND_EXTERNAL_SERVICE_LONG = 0x8000_0000_0000_0000L;
-
+ public static final long BIND_EXTERNAL_SERVICE_LONG = 1L << 62;
/**
* These bind flags reduce the strength of the binding such that we shouldn't
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index f8f2663063a6..eb14cc4802ad 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1156,6 +1156,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/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 08cfbf76a040..96a42e24bc1a 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -33,6 +33,7 @@ import android.content.pm.PackageInstaller;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
+import android.content.pm.LauncherActivityInfoInternal;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.UserHandle;
@@ -114,4 +115,5 @@ interface ILauncherApps {
String getShortcutIconUri(String callingPackage, String packageName, String shortcutId,
int userId);
+ Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage, int userId);
}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 16e720e3794c..a4d532712cfe 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -34,7 +34,6 @@ import android.util.DisplayMetrics;
*/
public class LauncherActivityInfo {
private final PackageManager mPm;
- private UserHandle mUser;
private final LauncherActivityInfoInternal mInternal;
/**
@@ -43,9 +42,8 @@ public class LauncherActivityInfo {
* @param context The context for fetching resources.
*/
- LauncherActivityInfo(Context context, UserHandle user, LauncherActivityInfoInternal internal) {
+ LauncherActivityInfo(Context context, LauncherActivityInfoInternal internal) {
mPm = context.getPackageManager();
- mUser = user;
mInternal = internal;
}
@@ -70,7 +68,7 @@ public class LauncherActivityInfo {
* @return The UserHandle of the profile.
*/
public UserHandle getUser() {
- return mUser;
+ return mInternal.getUser();
}
/**
@@ -180,6 +178,6 @@ public class LauncherActivityInfo {
public Drawable getBadgedIcon(int density) {
Drawable originalIcon = getIcon(density);
- return mPm.getUserBadgedIcon(originalIcon, mUser);
+ return mPm.getUserBadgedIcon(originalIcon, mInternal.getUser());
}
}
diff --git a/core/java/android/content/pm/LauncherActivityInfoInternal.java b/core/java/android/content/pm/LauncherActivityInfoInternal.java
index 46c415df7525..5aac97d784b3 100644
--- a/core/java/android/content/pm/LauncherActivityInfoInternal.java
+++ b/core/java/android/content/pm/LauncherActivityInfoInternal.java
@@ -21,6 +21,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
/**
* @hide
@@ -30,23 +31,27 @@ public class LauncherActivityInfoInternal implements Parcelable {
@NonNull private ActivityInfo mActivityInfo;
@NonNull private ComponentName mComponentName;
@NonNull private IncrementalStatesInfo mIncrementalStatesInfo;
+ @NonNull private UserHandle mUser;
/**
* @param info ActivityInfo from which to create the LauncherActivityInfo.
* @param incrementalStatesInfo The package's states.
+ * @param user The user the activity info belongs to.
*/
public LauncherActivityInfoInternal(@NonNull ActivityInfo info,
- @NonNull IncrementalStatesInfo incrementalStatesInfo) {
+ @NonNull IncrementalStatesInfo incrementalStatesInfo,
+ @NonNull UserHandle user) {
mActivityInfo = info;
mComponentName = new ComponentName(info.packageName, info.name);
mIncrementalStatesInfo = incrementalStatesInfo;
+ mUser = user;
}
public LauncherActivityInfoInternal(Parcel source) {
- mActivityInfo = source.readParcelable(ActivityInfo.class.getClassLoader(), android.content.pm.ActivityInfo.class);
+ mActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
mComponentName = new ComponentName(mActivityInfo.packageName, mActivityInfo.name);
- mIncrementalStatesInfo = source.readParcelable(
- IncrementalStatesInfo.class.getClassLoader(), android.content.pm.IncrementalStatesInfo.class);
+ mIncrementalStatesInfo = source.readTypedObject(IncrementalStatesInfo.CREATOR);
+ mUser = source.readTypedObject(UserHandle.CREATOR);
}
public ComponentName getComponentName() {
@@ -57,6 +62,10 @@ public class LauncherActivityInfoInternal implements Parcelable {
return mActivityInfo;
}
+ public UserHandle getUser() {
+ return mUser;
+ }
+
public IncrementalStatesInfo getIncrementalStatesInfo() {
return mIncrementalStatesInfo;
}
@@ -68,8 +77,9 @@ public class LauncherActivityInfoInternal implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeParcelable(mActivityInfo, 0);
- dest.writeParcelable(mIncrementalStatesInfo, 0);
+ dest.writeTypedObject(mActivityInfo, flags);
+ dest.writeTypedObject(mIncrementalStatesInfo, flags);
+ dest.writeTypedObject(mUser, flags);
}
public static final @android.annotation.NonNull Creator<LauncherActivityInfoInternal> CREATOR =
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index f8c49744d834..8989006a7e83 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -64,6 +64,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Pair;
@@ -793,13 +794,45 @@ public class LauncherApps {
if (ai == null) {
return null;
}
- return new LauncherActivityInfo(mContext, user, ai);
+ return new LauncherActivityInfo(mContext, ai);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
+ * Returns overrides for the activities that should be launched for the shortcuts of certain
+ * package names.
+ *
+ * @return {@link Map} whose keys are package names and whose values are the
+ * {@link LauncherActivityInfo}s that should be used for those packages' shortcuts. If there are
+ * no activity overrides, an empty {@link Map} will be returned.
+ *
+ * @hide
+ */
+ @NonNull
+ public Map<String, LauncherActivityInfo> getActivityOverrides() {
+ Map<String, LauncherActivityInfo> activityOverrides = new ArrayMap<>();
+ try {
+ Map<String, LauncherActivityInfoInternal> activityOverridesInternal =
+ mService.getActivityOverrides(mContext.getPackageName(), mContext.getUserId());
+ for (Map.Entry<String, LauncherActivityInfoInternal> packageToOverride :
+ activityOverridesInternal.entrySet()) {
+ activityOverrides.put(
+ packageToOverride.getKey(),
+ new LauncherActivityInfo(
+ mContext,
+ packageToOverride.getValue()
+ )
+ );
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ return activityOverrides;
+ }
+
+ /**
* Starts a Main activity in the specified profile.
*
* @param component The ComponentName of the activity to launch
@@ -916,7 +949,7 @@ public class LauncherApps {
}
ArrayList<LauncherActivityInfo> lais = new ArrayList<>();
for (LauncherActivityInfoInternal internal : internals.getList()) {
- LauncherActivityInfo lai = new LauncherActivityInfo(mContext, user, internal);
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, internal);
if (DEBUG) {
Log.v(TAG, "Returning activity for profile " + user + " : "
+ lai.getComponentName());
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index a6a62150d0da..cb988dfdb203 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2935,13 +2935,14 @@ public class PackageInstaller {
* <li>The {@link InstallSourceInfo#getUpdateOwnerPackageName() update owner}
* of an existing version of the app (in other words, this install session is
* an app update) if the update ownership enforcement is enabled.</li>
- * <li>The {@link InstallSourceInfo#getInstallingPackageName() installer of
- * record} of an existing version of the app (in other words, this install
+ * <li>The
+ * {@link InstallSourceInfo#getInstallingPackageName() installer of record}
+ * of an existing version of the app (in other words, this install
* session is an app update) if the update ownership enforcement isn't
* enabled.</li>
* <li>Updating itself.</li>
* </ul>
- * </li>>
+ * </li>
* <li>The installer declares the
* {@link android.Manifest.permission#UPDATE_PACKAGES_WITHOUT_USER_ACTION
* UPDATE_PACKAGES_WITHOUT_USER_ACTION} permission.</li>
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index f0230e7f4593..0806f1db2bb7 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -26,12 +26,12 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
-import android.content.pm.ServiceInfo;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -75,21 +75,21 @@ public final class CredentialManager {
*
* @hide
*/
- public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;
+ @TestApi public static final int PROVIDER_FILTER_ALL_PROVIDERS = 0;
/**
* Returns system credential providers only.
*
* @hide
*/
- public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;
+ @TestApi public static final int PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY = 1;
/**
* Returns user credential providers only.
*
* @hide
*/
- public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;
+ @TestApi public static final int PROVIDER_FILTER_USER_PROVIDERS_ONLY = 2;
private final Context mContext;
private final ICredentialManager mService;
@@ -263,44 +263,6 @@ public final class CredentialManager {
}
/**
- * Gets a list of all user configurable credential providers registered on the system. This API
- * is intended for browsers and settings apps.
- *
- * @param cancellationSignal an optional signal that allows for cancelling this call
- * @param executor the callback will take place on this {@link Executor}
- * @param callback the callback invoked when the request succeeds or fails
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- public void listEnabledProviders(
- @Nullable CancellationSignal cancellationSignal,
- @CallbackExecutor @NonNull Executor executor,
- @NonNull
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- callback) {
- requireNonNull(executor, "executor must not be null");
- requireNonNull(callback, "callback must not be null");
-
- if (cancellationSignal != null && cancellationSignal.isCanceled()) {
- Log.w(TAG, "listEnabledProviders already canceled");
- return;
- }
-
- ICancellationSignal cancelRemote = null;
- try {
- cancelRemote =
- mService.listEnabledProviders(
- new ListEnabledProvidersTransport(executor, callback));
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
- }
-
- if (cancellationSignal != null && cancelRemote != null) {
- cancellationSignal.setRemote(cancelRemote);
- }
- }
-
- /**
* Sets a list of all user configurable credential providers registered on the system. This API
* is intended for settings apps.
*
@@ -348,36 +310,43 @@ public final class CredentialManager {
}
/**
- * Returns the list of ServiceInfo for all discovered credential providers on this device.
+ * Returns the list of CredentialProviderInfo for all discovered credential providers on this
+ * device but will include test system providers as well.
*
* @hide
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
- public List<ServiceInfo> getCredentialProviderServicesForTesting(
- @ProviderFilter int providerFilter) {
+ @TestApi
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.QUERY_ALL_PACKAGES,
+ android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
+ })
+ public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ @ProviderFilter int providerFilter) {
try {
- return mService.getCredentialProviderServices(
- mContext.getUserId(),
- /* disableSystemAppVerificationForTests= */ true,
- providerFilter);
+ return mService.getCredentialProviderServicesForTesting(providerFilter);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Returns the list of ServiceInfo for all discovered credential providers on this device.
+ * Returns the list of CredentialProviderInfo for all discovered credential providers on this
+ * device.
*
* @hide
*/
@NonNull
- @RequiresPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)
- public List<ServiceInfo> getCredentialProviderServices(
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.QUERY_ALL_PACKAGES,
+ android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS
+ })
+ public List<CredentialProviderInfo> getCredentialProviderServices(
int userId, @ProviderFilter int providerFilter) {
try {
- return mService.getCredentialProviderServices(
- userId, /* disableSystemAppVerificationForTests= */ false, providerFilter);
+ return mService.getCredentialProviderServices(userId, providerFilter);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -388,7 +357,9 @@ public final class CredentialManager {
*
* @hide
*/
- public static boolean isServiceEnabled(Context context) {
+ @TestApi
+ public static boolean isServiceEnabled(@NonNull Context context) {
+ requireNonNull(context, "context must not be null");
if (context == null) {
return false;
}
@@ -578,33 +549,6 @@ public final class CredentialManager {
}
}
- private static class ListEnabledProvidersTransport extends IListEnabledProvidersCallback.Stub {
- // TODO: listen for cancellation to release callback.
-
- private final Executor mExecutor;
- private final OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- mCallback;
-
- private ListEnabledProvidersTransport(
- Executor executor,
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException>
- callback) {
- mExecutor = executor;
- mCallback = callback;
- }
-
- @Override
- public void onResponse(ListEnabledProvidersResponse response) {
- mExecutor.execute(() -> mCallback.onResult(response));
- }
-
- @Override
- public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new ListEnabledProvidersException(errorType, message)));
- }
- }
-
private static class SetEnabledProvidersTransport extends ISetEnabledProvidersCallback.Stub {
// TODO: listen for cancellation to release callback.
diff --git a/core/java/android/credentials/IListEnabledProvidersCallback.aidl b/core/java/android/credentials/CredentialProviderInfo.aidl
index 3a8e25ed954a..30b7742d17f8 100644
--- a/core/java/android/credentials/IListEnabledProvidersCallback.aidl
+++ b/core/java/android/credentials/CredentialProviderInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 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.
@@ -16,14 +16,4 @@
package android.credentials;
-import android.credentials.ListEnabledProvidersResponse;
-
-/**
- * Listener for an listEnabledProviders request.
- *
- * @hide
- */
-interface IListEnabledProvidersCallback {
- oneway void onResponse(in ListEnabledProvidersResponse response);
- oneway void onError(String errorType, String message);
-} \ No newline at end of file
+parcelable CredentialProviderInfo; \ No newline at end of file
diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java
new file mode 100644
index 000000000000..7276770d281e
--- /dev/null
+++ b/core/java/android/credentials/CredentialProviderInfo.java
@@ -0,0 +1,215 @@
+/*
+ * 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 android.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@link ServiceInfo} and meta-data about a credential provider.
+ *
+ * @hide
+ */
+@TestApi
+public final class CredentialProviderInfo implements Parcelable {
+ @NonNull private final ServiceInfo mServiceInfo;
+ @NonNull private final List<String> mCapabilities = new ArrayList<>();
+ @Nullable private final CharSequence mOverrideLabel;
+ private final boolean mIsSystemProvider;
+ private final boolean mIsEnabled;
+
+ /**
+ * Constructs an information instance of the credential provider.
+ *
+ * @param builder the builder object.
+ */
+ private CredentialProviderInfo(@NonNull Builder builder) {
+ mServiceInfo = builder.mServiceInfo;
+ mCapabilities.addAll(builder.mCapabilities);
+ mIsSystemProvider = builder.mIsSystemProvider;
+ mIsEnabled = builder.mIsEnabled;
+ mOverrideLabel = builder.mOverrideLabel;
+ }
+
+ /** Returns true if the service supports the given {@code credentialType}, false otherwise. */
+ @NonNull
+ public boolean hasCapability(@NonNull String credentialType) {
+ return mCapabilities.contains(credentialType);
+ }
+
+ /** Returns the service info. */
+ @NonNull
+ public ServiceInfo getServiceInfo() {
+ return mServiceInfo;
+ }
+
+ /** Returns whether it is a system provider. */
+ public boolean isSystemProvider() {
+ return mIsSystemProvider;
+ }
+
+ /** Returns the service icon. */
+ @Nullable
+ public Drawable getServiceIcon(@NonNull Context context) {
+ return mServiceInfo.loadIcon(context.getPackageManager());
+ }
+
+ /** Returns the service label. */
+ @Nullable
+ public CharSequence getLabel(@NonNull Context context) {
+ if (mOverrideLabel != null) {
+ return mOverrideLabel;
+ }
+ return mServiceInfo.loadSafeLabel(context.getPackageManager());
+ }
+
+ /** Returns a list of capabilities this provider service can support. */
+ @NonNull
+ public List<String> getCapabilities() {
+ return Collections.unmodifiableList(mCapabilities);
+ }
+
+ /** Returns whether the provider is enabled by the user. */
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ /** Returns the component name for the service. */
+ @NonNull
+ public ComponentName getComponentName() {
+ return mServiceInfo.getComponentName();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeTypedObject(mServiceInfo, flags);
+ dest.writeBoolean(mIsSystemProvider);
+ dest.writeStringList(mCapabilities);
+ dest.writeBoolean(mIsEnabled);
+ TextUtils.writeToParcel(mOverrideLabel, dest, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "CredentialProviderInfo {"
+ + "serviceInfo="
+ + mServiceInfo
+ + ", "
+ + "isSystemProvider="
+ + mIsSystemProvider
+ + ", "
+ + "isEnabled="
+ + mIsEnabled
+ + ", "
+ + "overrideLabel="
+ + mOverrideLabel
+ + ", "
+ + "capabilities="
+ + String.join(",", mCapabilities)
+ + "}";
+ }
+
+ private CredentialProviderInfo(@NonNull Parcel in) {
+ mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR);
+ mIsSystemProvider = in.readBoolean();
+ in.readStringList(mCapabilities);
+ mIsEnabled = in.readBoolean();
+ mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+
+ public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR =
+ new Parcelable.Creator<CredentialProviderInfo>() {
+ @Override
+ public CredentialProviderInfo[] newArray(int size) {
+ return new CredentialProviderInfo[size];
+ }
+
+ @Override
+ public CredentialProviderInfo createFromParcel(@NonNull Parcel in) {
+ return new CredentialProviderInfo(in);
+ }
+ };
+
+ /** A builder for {@link CredentialProviderInfo} objects. */
+ public static final class Builder {
+
+ @NonNull private ServiceInfo mServiceInfo;
+ @NonNull private List<String> mCapabilities = new ArrayList<>();
+ private boolean mIsSystemProvider = false;
+ private boolean mIsEnabled = false;
+ @Nullable private CharSequence mOverrideLabel = null;
+
+ /**
+ * Creates a new builder.
+ *
+ * @param serviceInfo the service info of the credential provider service.
+ */
+ public Builder(@NonNull ServiceInfo serviceInfo) {
+ mServiceInfo = serviceInfo;
+ }
+
+ /** Sets whether it is a system provider. */
+ public @NonNull Builder setSystemProvider(boolean isSystemProvider) {
+ mIsSystemProvider = isSystemProvider;
+ return this;
+ }
+
+ /**
+ * Sets the label to be used instead of getting from the system (for unit tests).
+ *
+ * @hide
+ */
+ public @NonNull Builder setOverrideLabel(@NonNull CharSequence overrideLabel) {
+ mOverrideLabel = overrideLabel;
+ return this;
+ }
+
+ /** Sets a list of capabilities this provider service can support. */
+ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) {
+ mCapabilities.addAll(capabilities);
+ return this;
+ }
+
+ /** Sets whether it is enabled by the user. */
+ public @NonNull Builder setEnabled(boolean isEnabled) {
+ mIsEnabled = isEnabled;
+ return this;
+ }
+
+ /** Builds a new {@link CredentialProviderInfo} instance. */
+ public @NonNull CredentialProviderInfo build() {
+ return new CredentialProviderInfo(this);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index 625fc8ab5dad..8c2cb5aa0d77 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,7 +18,7 @@ package android.credentials;
import java.util.List;
-import android.content.pm.ServiceInfo;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCredentialRequest;
@@ -27,7 +27,6 @@ import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
import android.content.ComponentName;
import android.os.ICancellationSignal;
@@ -45,8 +44,6 @@ interface ICredentialManager {
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
- @nullable ICancellationSignal listEnabledProviders(in IListEnabledProvidersCallback callback);
-
void setEnabledProviders(in List<String> providers, in int userId, in ISetEnabledProvidersCallback callback);
void registerCredentialDescription(in RegisterCredentialDescriptionRequest request, String callingPackage);
@@ -55,6 +52,8 @@ interface ICredentialManager {
boolean isEnabledCredentialProviderService(in ComponentName componentName, String callingPackage);
- List<ServiceInfo> getCredentialProviderServices(in int userId, in boolean disableSystemAppVerificationForTests, in int providerFilter);
+ List<CredentialProviderInfo> getCredentialProviderServices(in int userId, in int providerFilter);
+
+ List<CredentialProviderInfo> getCredentialProviderServicesForTesting(in int providerFilter);
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 50dd7a0bc1be..6ae71d2bb25e 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1409,9 +1409,10 @@ public final class DisplayManager {
* @param hdrConversionMode The {@link HdrConversionMode} to set.
* Note, {@code HdrConversionMode.preferredHdrOutputType} is only applicable when
* {@code HdrConversionMode.conversionMode} is {@link HdrConversionMode#HDR_CONVERSION_FORCE}.
+ * If {@code HdrConversionMode.preferredHdrOutputType} is not set in case when
+ * {@code HdrConversionMode.conversionMode} is {@link HdrConversionMode#HDR_CONVERSION_FORCE},
+ * it means that preferred output type is SDR.
*
- * @throws IllegalArgumentException if hdrConversionMode.preferredHdrOutputType is not set
- * when hdrConversionMode.conversionMode is {@link HdrConversionMode#HDR_CONVERSION_FORCE}.
* @throws IllegalArgumentException if hdrConversionMode.preferredHdrOutputType is set but
* hdrConversionMode.conversionMode is not {@link HdrConversionMode#HDR_CONVERSION_FORCE}.
*
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 6b56a067a198..490e55ba260f 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -27,13 +27,14 @@ import android.media.projection.MediaProjection;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.Surface;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Holds configuration used to create {@link VirtualDisplay} instances.
@@ -51,8 +52,10 @@ public final class VirtualDisplayConfig implements Parcelable {
private final Surface mSurface;
private final String mUniqueId;
private final int mDisplayIdToMirror;
- private final boolean mWindowManagerMirroring;
- private ArrayList<String> mDisplayCategories = null;
+ private final boolean mWindowManagerMirroringEnabled;
+ private ArraySet<String> mDisplayCategories = null;
+ @Nullable
+ private ContentRecordingSession mContentRecordingSession;
private final float mRequestedRefreshRate;
private VirtualDisplayConfig(
@@ -64,8 +67,9 @@ public final class VirtualDisplayConfig implements Parcelable {
@Nullable Surface surface,
@Nullable String uniqueId,
int displayIdToMirror,
- boolean windowManagerMirroring,
- @NonNull ArrayList<String> displayCategories,
+ boolean windowManagerMirroringEnabled,
+ ContentRecordingSession session,
+ @NonNull ArraySet<String> displayCategories,
float requestedRefreshRate) {
mName = name;
mWidth = width;
@@ -75,7 +79,8 @@ public final class VirtualDisplayConfig implements Parcelable {
mSurface = surface;
mUniqueId = uniqueId;
mDisplayIdToMirror = displayIdToMirror;
- mWindowManagerMirroring = windowManagerMirroring;
+ mWindowManagerMirroringEnabled = windowManagerMirroringEnabled;
+ mContentRecordingSession = session;
mDisplayCategories = displayCategories;
mRequestedRefreshRate = requestedRefreshRate;
}
@@ -151,8 +156,19 @@ public final class VirtualDisplayConfig implements Parcelable {
* if DisplayManager should record contents instead.
* @hide
*/
- public boolean isWindowManagerMirroring() {
- return mWindowManagerMirroring;
+ public boolean isWindowManagerMirroringEnabled() {
+ return mWindowManagerMirroringEnabled;
+ }
+
+ /**
+ * Returns the recording session associated with this VirtualDisplay. Only used for
+ * recording via {@link MediaProjection}.
+ *
+ * @hide
+ */
+ @Nullable
+ public ContentRecordingSession getContentRecordingSession() {
+ return mContentRecordingSession;
}
/**
@@ -161,8 +177,8 @@ public final class VirtualDisplayConfig implements Parcelable {
* @see Builder#setDisplayCategories
*/
@NonNull
- public List<String> getDisplayCategories() {
- return Collections.unmodifiableList(mDisplayCategories);
+ public Set<String> getDisplayCategories() {
+ return Collections.unmodifiableSet(mDisplayCategories);
}
/**
@@ -185,8 +201,9 @@ public final class VirtualDisplayConfig implements Parcelable {
dest.writeTypedObject(mSurface, flags);
dest.writeString8(mUniqueId);
dest.writeInt(mDisplayIdToMirror);
- dest.writeBoolean(mWindowManagerMirroring);
- dest.writeStringList(mDisplayCategories);
+ dest.writeBoolean(mWindowManagerMirroringEnabled);
+ dest.writeTypedObject(mContentRecordingSession, flags);
+ dest.writeArraySet(mDisplayCategories);
dest.writeFloat(mRequestedRefreshRate);
}
@@ -210,7 +227,8 @@ public final class VirtualDisplayConfig implements Parcelable {
&& Objects.equals(mSurface, that.mSurface)
&& Objects.equals(mUniqueId, that.mUniqueId)
&& mDisplayIdToMirror == that.mDisplayIdToMirror
- && mWindowManagerMirroring == that.mWindowManagerMirroring
+ && mWindowManagerMirroringEnabled == that.mWindowManagerMirroringEnabled
+ && Objects.equals(mContentRecordingSession, that.mContentRecordingSession)
&& Objects.equals(mDisplayCategories, that.mDisplayCategories)
&& mRequestedRefreshRate == that.mRequestedRefreshRate;
}
@@ -219,8 +237,8 @@ public final class VirtualDisplayConfig implements Parcelable {
public int hashCode() {
int hashCode = Objects.hash(
mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
- mDisplayIdToMirror, mWindowManagerMirroring, mDisplayCategories,
- mRequestedRefreshRate);
+ mDisplayIdToMirror, mWindowManagerMirroringEnabled, mContentRecordingSession,
+ mDisplayCategories, mRequestedRefreshRate);
return hashCode;
}
@@ -236,7 +254,8 @@ public final class VirtualDisplayConfig implements Parcelable {
+ " mSurface=" + mSurface
+ " mUniqueId=" + mUniqueId
+ " mDisplayIdToMirror=" + mDisplayIdToMirror
- + " mWindowManagerMirroring=" + mWindowManagerMirroring
+ + " mWindowManagerMirroringEnabled=" + mWindowManagerMirroringEnabled
+ + " mContentRecordingSession=" + mContentRecordingSession
+ " mDisplayCategories=" + mDisplayCategories
+ " mRequestedRefreshRate=" + mRequestedRefreshRate
+ ")";
@@ -251,9 +270,9 @@ public final class VirtualDisplayConfig implements Parcelable {
mSurface = in.readTypedObject(Surface.CREATOR);
mUniqueId = in.readString8();
mDisplayIdToMirror = in.readInt();
- mWindowManagerMirroring = in.readBoolean();
- mDisplayCategories = new ArrayList<>();
- in.readStringList(mDisplayCategories);
+ mWindowManagerMirroringEnabled = in.readBoolean();
+ mContentRecordingSession = in.readTypedObject(ContentRecordingSession.CREATOR);
+ mDisplayCategories = (ArraySet<String>) in.readArraySet(null);
mRequestedRefreshRate = in.readFloat();
}
@@ -283,8 +302,10 @@ public final class VirtualDisplayConfig implements Parcelable {
private Surface mSurface = null;
private String mUniqueId = null;
private int mDisplayIdToMirror = DEFAULT_DISPLAY;
- private boolean mWindowManagerMirroring = false;
- private ArrayList<String> mDisplayCategories = new ArrayList<>();
+ private boolean mWindowManagerMirroringEnabled = false;
+ @Nullable
+ private ContentRecordingSession mContentRecordingSession;
+ private ArraySet<String> mDisplayCategories = new ArraySet<>();
private float mRequestedRefreshRate = 0.0f;
/**
@@ -370,8 +391,20 @@ public final class VirtualDisplayConfig implements Parcelable {
* @hide
*/
@NonNull
- public Builder setWindowManagerMirroring(boolean windowManagerMirroring) {
- mWindowManagerMirroring = windowManagerMirroring;
+ public Builder setWindowManagerMirroringEnabled(boolean windowManagerMirroringEnabled) {
+ mWindowManagerMirroringEnabled = windowManagerMirroringEnabled;
+ return this;
+ }
+
+ /**
+ * Sets the recording session associated with this {@link VirtualDisplay}. Only used for
+ * recording via {@link MediaProjection}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setContentRecordingSession(@Nullable ContentRecordingSession session) {
+ mContentRecordingSession = session;
return this;
}
@@ -383,7 +416,7 @@ public final class VirtualDisplayConfig implements Parcelable {
* {@link android.content.pm.ActivityInfo#requiredDisplayCategory}.
*/
@NonNull
- public Builder setDisplayCategories(@NonNull List<String> displayCategories) {
+ public Builder setDisplayCategories(@NonNull Set<String> displayCategories) {
mDisplayCategories.clear();
mDisplayCategories.addAll(Objects.requireNonNull(displayCategories));
return this;
@@ -435,7 +468,8 @@ public final class VirtualDisplayConfig implements Parcelable {
mSurface,
mUniqueId,
mDisplayIdToMirror,
- mWindowManagerMirroring,
+ mWindowManagerMirroringEnabled,
+ mContentRecordingSession,
mDisplayCategories,
mRequestedRefreshRate);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index e2af9b03d7f5..cacde7f4a547 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -26,6 +26,8 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.ActivityThread;
+import android.app.OnActivityPausedListener;
import android.app.PendingIntent;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -420,6 +422,7 @@ public final class NfcAdapter {
// Guarded by NfcAdapter.class
static boolean sIsInitialized = false;
static boolean sHasNfcFeature;
+ static boolean sHasCeFeature;
// Final after first constructor, except for
// attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
@@ -616,11 +619,13 @@ public final class NfcAdapter {
PackageManager pm;
pm = context.getPackageManager();
sHasNfcFeature = pm.hasSystemFeature(PackageManager.FEATURE_NFC);
- boolean hasHceFeature =
+ sHasCeFeature =
pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)
- || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF);
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC)
+ || pm.hasSystemFeature(PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
/* is this device meant to have NFC */
- if (!sHasNfcFeature && !hasHceFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
Log.v(TAG, "this device does not have NFC support");
throw new UnsupportedOperationException();
}
@@ -643,7 +648,7 @@ public final class NfcAdapter {
throw new UnsupportedOperationException();
}
}
- if (hasHceFeature) {
+ if (sHasCeFeature) {
try {
sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
} catch (RemoteException e) {
@@ -1467,11 +1472,17 @@ public final class NfcAdapter {
if (activity == null || intent == null) {
throw new NullPointerException();
}
+ if (!activity.isResumed()) {
+ throw new IllegalStateException("Foreground dispatch can only be enabled " +
+ "when your activity is resumed");
+ }
try {
TechListParcel parcel = null;
if (techLists != null && techLists.length > 0) {
parcel = new TechListParcel(techLists);
}
+ ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity,
+ mForegroundDispatchListener);
sService.setForegroundDispatch(intent, filters, parcel);
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
@@ -1499,8 +1510,25 @@ public final class NfcAdapter {
throw new UnsupportedOperationException();
}
}
+ ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity,
+ mForegroundDispatchListener);
+ disableForegroundDispatchInternal(activity, false);
+ }
+
+ OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() {
+ @Override
+ public void onPaused(Activity activity) {
+ disableForegroundDispatchInternal(activity, true);
+ }
+ };
+
+ void disableForegroundDispatchInternal(Activity activity, boolean force) {
try {
sService.setForegroundDispatch(null, null, null);
+ if (!force && !activity.isResumed()) {
+ throw new IllegalStateException("You must disable foreground dispatching " +
+ "while your activity is still resumed");
+ }
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
}
@@ -1669,7 +1697,7 @@ public final class NfcAdapter {
@SystemApi
@RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
public boolean enableSecureNfc(boolean enable) {
- if (!sHasNfcFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
try {
@@ -1694,10 +1722,13 @@ public final class NfcAdapter {
* Checks if the device supports Secure NFC functionality.
*
* @return True if device supports Secure NFC, false otherwise
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
*/
public boolean isSecureNfcSupported() {
- if (!sHasNfcFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
try {
@@ -1723,11 +1754,14 @@ public final class NfcAdapter {
* such as their relative positioning on the device.
*
* @return Information on the nfc antenna(s) on the device.
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
*/
@Nullable
public NfcAntennaInfo getNfcAntennaInfo() {
- if (!sHasNfcFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
try {
@@ -1752,12 +1786,15 @@ public final class NfcAdapter {
* Checks Secure NFC feature is enabled.
*
* @return True if Secure NFC is enabled, false otherwise
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
* @throws UnsupportedOperationException if device doesn't support
* Secure NFC functionality. {@link #isSecureNfcSupported}
*/
public boolean isSecureNfcEnabled() {
- if (!sHasNfcFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
try {
@@ -2071,14 +2108,17 @@ public final class NfcAdapter {
* always on.
* @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
* disabled), if false the NFCC will follow completely the Nfc adapter state.
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
* @return void
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
public boolean setControllerAlwaysOn(boolean value) {
- if (!sHasNfcFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
try {
@@ -2103,7 +2143,10 @@ public final class NfcAdapter {
* Checks NFC controller always on feature is enabled.
*
* @return True if NFC controller always on is enabled, false otherwise
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
* @hide
*/
@SystemApi
@@ -2131,13 +2174,16 @@ public final class NfcAdapter {
* Checks if the device supports NFC controller always on functionality.
*
* @return True if device supports NFC controller always on, false otherwise
- * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
+ * @throws UnsupportedOperationException if FEATURE_NFC,
+ * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF,
+ * FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC and FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE
+ * are unavailable
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
public boolean isControllerAlwaysOnSupported() {
- if (!sHasNfcFeature) {
+ if (!sHasNfcFeature && !sHasCeFeature) {
throw new UnsupportedOperationException();
}
try {
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 07d500176fe5..5b527c70b4f7 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -979,6 +979,19 @@ public abstract class DocumentsProvider extends ContentProvider {
}
/**
+ * An unrestricted version of getType, which does not reveal sensitive information
+ */
+ @Override
+ public final @Nullable String getTypeAnonymous(@NonNull Uri uri) {
+ switch (mMatcher.match(uri)) {
+ case MATCH_ROOT:
+ return DocumentsContract.Root.MIME_TYPE_ITEM;
+ default:
+ return null;
+ }
+ }
+
+ /**
* Implementation is provided by the parent class. Can be overridden to
* provide additional functionality, but subclasses <em>must</em> always
* call the superclass. If the superclass returns {@code null}, the subclass
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef007746a67f..045ba1f82b7c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5678,6 +5678,36 @@ public final class Settings {
public static final String LOCALE_PREFERENCES = "locale_preferences";
/**
+ * Setting to enable camera flash notification feature.
+ * <ul>
+ * <li> 0 = Off
+ * <li> 1 = On
+ * </ul>
+ * @hide
+ */
+ public static final String CAMERA_FLASH_NOTIFICATION = "camera_flash_notification";
+
+ /**
+ * Setting to enable screen flash notification feature.
+ * <ul>
+ * <li> 0 = Off
+ * <li> 1 = On
+ * </ul>
+ * @hide
+ */
+ public static final String SCREEN_FLASH_NOTIFICATION = "screen_flash_notification";
+
+ /**
+ * Integer property that specifes the color for screen flash notification as a
+ * packed 32-bit color.
+ *
+ * @see android.graphics.Color#argb
+ * @hide
+ */
+ public static final String SCREEN_FLASH_NOTIFICATION_COLOR =
+ "screen_flash_notification_color_global";
+
+ /**
* IMPORTANT: If you add a new public settings you also have to add it to
* PUBLIC_SETTINGS below. If the new setting is hidden you have to add
* it to PRIVATE_SETTINGS below. Also add a validator that can validate
@@ -5810,6 +5840,9 @@ public final class Settings {
PRIVATE_SETTINGS.add(TOUCHPAD_NATURAL_SCROLLING);
PRIVATE_SETTINGS.add(TOUCHPAD_TAP_TO_CLICK);
PRIVATE_SETTINGS.add(TOUCHPAD_RIGHT_CLICK_ZONE);
+ PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
+ PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
+ PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
}
/**
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index d943bf9ac872..0ef8bb64acaf 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -29,6 +29,7 @@ import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
@@ -283,24 +284,28 @@ public final class Dataset implements Parcelable {
}
/** @hide */
- public RemoteViews getFieldPresentation(int index) {
+ @TestApi
+ public @Nullable RemoteViews getFieldPresentation(int index) {
final RemoteViews customPresentation = mFieldPresentations.get(index);
return customPresentation != null ? customPresentation : mPresentation;
}
/** @hide */
- public RemoteViews getFieldDialogPresentation(int index) {
+ @TestApi
+ public @Nullable RemoteViews getFieldDialogPresentation(int index) {
final RemoteViews customPresentation = mFieldDialogPresentations.get(index);
return customPresentation != null ? customPresentation : mDialogPresentation;
}
/** @hide */
+ @TestApi
public @Nullable InlinePresentation getFieldInlinePresentation(int index) {
final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index);
return inlinePresentation != null ? inlinePresentation : mInlinePresentation;
}
/** @hide */
+ @TestApi
public @Nullable InlinePresentation getFieldInlineTooltipPresentation(int index) {
final InlinePresentation inlineTooltipPresentation =
mFieldInlineTooltipPresentations.get(index);
@@ -309,6 +314,7 @@ public final class Dataset implements Parcelable {
}
/** @hide */
+ @TestApi
public @Nullable DatasetFieldFilter getFilter(int index) {
return mFieldFilters.get(index);
}
@@ -389,6 +395,9 @@ public final class Dataset implements Parcelable {
if (mAuthentication != null) {
builder.append(", hasAuthentication");
}
+ if (mAutofillDatatypes != null) {
+ builder.append(", autofillDatatypes=").append(mAutofillDatatypes);
+ }
return builder.append(']').toString();
}
@@ -1090,8 +1099,7 @@ public final class Dataset implements Parcelable {
*
* @return this builder.
*/
- public @NonNull Dataset.Builder setField(
- @NonNull String hint, @NonNull Field field) {
+ public @NonNull Dataset.Builder setField(@NonNull String hint, @NonNull Field field) {
throwIfDestroyed();
final DatasetFieldFilter filter = field.getDatasetFieldFilter();
@@ -1111,6 +1119,23 @@ public final class Dataset implements Parcelable {
}
/**
+ * Adds a field to this Dataset that is relevant to all applicable hints. This is used to
+ * provide field information when autofill with platform detections is enabled.
+ * Platform detections are on when receiving a populated list from
+ * FillRequest#getHints().
+ *
+ * @param field the fill information about the field.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called
+ * or this builder also contains AutofillId information
+ *
+ * @return this builder.
+ */
+ public @NonNull Dataset.Builder setFieldForAllHints(@NonNull Field field) {
+ return setField(AutofillManager.ANY_HINT, field);
+ }
+
+ /**
* Sets the value of a field with an <a href="#Filtering">explicit filter</a>, and using an
* {@link InlinePresentation} to visualize it as an inline suggestion.
*
@@ -1304,7 +1329,7 @@ public final class Dataset implements Parcelable {
parcel.createTypedArrayList(InlinePresentation.CREATOR);
final ArrayList<DatasetFieldFilter> filters =
parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
- final ArrayList<String> datatypes =
+ final ArrayList<String> autofillDatatypes =
parcel.createStringArrayList();
final ClipData fieldContent = parcel.readParcelable(null,
android.content.ClipData.class);
@@ -1341,9 +1366,9 @@ public final class Dataset implements Parcelable {
}
final int inlinePresentationsSize = inlinePresentations.size();
- if (ids.size() == 0 && datatypes.size() > 0) {
- for (int i = 0; i < ids.size(); i++) {
- final String datatype = datatypes.get(i);
+ if (ids.size() == 0 && autofillDatatypes.size() > 0) {
+ for (int i = 0; i < autofillDatatypes.size(); i++) {
+ final String datatype = autofillDatatypes.get(i);
final AutofillValue value = values.get(i);
final RemoteViews fieldPresentation = presentations.get(i);
final RemoteViews fieldDialogPresentation = dialogPresentations.get(i);
@@ -1393,8 +1418,10 @@ public final class Dataset implements Parcelable {
*
* @hide
*/
+ @TestApi
public static final class DatasetFieldFilter implements Parcelable {
+ /** @hide */
@Nullable
public final Pattern pattern;
@@ -1402,6 +1429,10 @@ public final class Dataset implements Parcelable {
this.pattern = pattern;
}
+ public @Nullable Pattern getPattern() {
+ return pattern;
+ }
+
@Override
public String toString() {
if (!sDebug) return super.toString();
@@ -1416,7 +1447,7 @@ public final class Dataset implements Parcelable {
}
@Override
- public void writeToParcel(Parcel parcel, int flags) {
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeSerializable(pattern);
}
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index eebd31c6fa96..cd53cb6afc71 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -16,8 +16,10 @@
package android.service.credentials;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.os.Parcel;
import android.os.Parcelable;
@@ -137,7 +139,17 @@ public final class BeginCreateCredentialResponse implements Parcelable {
* result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
* {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESPONSE} key should be populated
* with a {@link android.credentials.CreateCredentialResponse} object.
+ *
+ * <p> Note that as a provider service you will only be able to set a remote entry if :
+ * - Provider service possesses the
+ * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+ * - Provider service is configured as the provider that can provide remote entries.
+ *
+ * If the above conditions are not met, setting back {@link BeginCreateCredentialResponse}
+ * on the callback from {@link CredentialProviderService#onBeginCreateCredential}
+ * will throw a {@link SecurityException}.
*/
+ @RequiresPermission(Manifest.permission.PROVIDE_REMOTE_CREDENTIALS)
public @NonNull Builder setRemoteCreateEntry(@Nullable RemoteEntry remoteCreateEntry) {
mRemoteCreateEntry = remoteCreateEntry;
return this;
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index 97f5079393a0..e25b6869605d 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -16,8 +16,10 @@
package android.service.credentials;
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.os.Parcel;
import android.os.Parcelable;
@@ -154,7 +156,17 @@ public final class BeginGetCredentialResponse implements Parcelable {
* result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
* {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE} key should be populated
* with a {@link android.credentials.Credential} object.
+ *
+ * <p> Note that as a provider service you will only be able to set a remote entry if :
+ * - Provider service possesses the
+ * {@link Manifest.permission.PROVIDE_REMOTE_CREDENTIALS} permission.
+ * - Provider service is configured as the provider that can provide remote entries.
+ *
+ * If the above conditions are not met, setting back {@link BeginGetCredentialResponse}
+ * on the callback from {@link CredentialProviderService#onBeginGetCredential} will
+ * throw a {@link SecurityException}.
*/
+ @RequiresPermission(Manifest.permission.PROVIDE_REMOTE_CREDENTIALS)
public @NonNull Builder setRemoteCredentialEntry(@Nullable RemoteEntry
remoteCredentialEntry) {
mRemoteCredentialEntry = remoteCredentialEntry;
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 7e98bc7eb975..e9cebd2e6af7 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -27,6 +27,8 @@ import android.credentials.GetCredentialResponse;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
/**
* A credential entry that is to be displayed on the account selector that is presented to the
* user.
@@ -56,7 +58,7 @@ import android.os.Parcelable;
@SuppressLint("ParcelNotFinal")
public class CredentialEntry implements Parcelable {
/** The request option that corresponds to this entry. **/
- private final @Nullable BeginGetCredentialOption mBeginGetCredentialOption;
+ private final @Nullable String mBeginGetCredentialOptionId;
/** The type of the credential entry to be shown on the UI. */
private final @NonNull String mType;
@@ -72,19 +74,51 @@ public class CredentialEntry implements Parcelable {
* to respond to query phase {@link CredentialProviderService#onBeginGetCredential}
* credential retrieval requests.
*
+ * @param beginGetCredentialOptionId the beginGetCredentialOptionId to be retrieved from
+ * {@link BeginGetCredentialOption#getId()} - the request option for which this CredentialEntry
+ * is being constructed This helps maintain an association
+ * such that when the user selects this entry, providers can
+ * receive the complete corresponding
+ * {@link GetCredentialRequest}.
+ * @param type the type of the credential for which this credential entry is being created
+ * @param slice the slice containing the metadata to be shown on the UI. Must be
+ * constructed through the androidx.credentials jetpack library.
+ *
+ * @throws IllegalArgumentException If {@code beginGetCredentialOptionId} or {@code type}
+ * is null, or empty
+ */
+ public CredentialEntry(@NonNull String beginGetCredentialOptionId, @NonNull String type,
+ @NonNull Slice slice) {
+ mBeginGetCredentialOptionId = Preconditions.checkStringNotEmpty(
+ beginGetCredentialOptionId, "beginGetCredentialOptionId must not be "
+ + "null, or empty");
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be null, or "
+ + "empty");
+ mSlice = requireNonNull(slice, "slice must not be null");
+ }
+
+ /**
+ * Creates an entry that is associated with a {@link BeginGetCredentialOption} request.
+ * Providers must use this constructor when they extend from {@link CredentialProviderService}
+ * to respond to query phase {@link CredentialProviderService#onBeginGetCredential}
+ * credential retrieval requests.
+ *
* @param beginGetCredentialOption the request option for which this credential entry is
* being constructed This helps maintain an association,
* such that when the user selects this entry, providers
- * can receive the conmplete corresponding request.
+ * can receive the complete corresponding request.
* @param slice the slice containing the metadata to be shown on the UI. Must be
* constructed through the androidx.credentials jetpack library.
*/
public CredentialEntry(@NonNull BeginGetCredentialOption beginGetCredentialOption,
@NonNull Slice slice) {
- mBeginGetCredentialOption = requireNonNull(beginGetCredentialOption,
- "beginGetCredentialOption must not be null");
- mType = requireNonNull(mBeginGetCredentialOption.getType(),
- "type must not be null");
+ requireNonNull(beginGetCredentialOption, "beginGetCredentialOption must not"
+ + " be null");
+ mBeginGetCredentialOptionId = Preconditions.checkStringNotEmpty(
+ beginGetCredentialOption.getId(), "Id in beginGetCredentialOption "
+ + "must not be null");
+ mType = Preconditions.checkStringNotEmpty(beginGetCredentialOption.getType(),
+ "type in beginGetCredentialOption must not be null");
mSlice = requireNonNull(slice, "slice must not be null");
}
@@ -97,11 +131,9 @@ public class CredentialEntry implements Parcelable {
* @param slice the slice containing the metadata to be shown on the UI. Must be
* constructed through the androidx.credentials jetpack library.
*
- * @hide
*/
- // TODO: Unhide this constructor when the registry APIs are stable
public CredentialEntry(@NonNull String type, @NonNull Slice slice) {
- mBeginGetCredentialOption = null;
+ mBeginGetCredentialOptionId = null;
mType = requireNonNull(type, "type must not be null");
mSlice = requireNonNull(slice, "slice must not be null");
}
@@ -110,7 +142,7 @@ public class CredentialEntry implements Parcelable {
requireNonNull(in, "parcel must not be null");
mType = in.readString8();
mSlice = in.readTypedObject(Slice.CREATOR);
- mBeginGetCredentialOption = in.readTypedObject(BeginGetCredentialOption.CREATOR);
+ mBeginGetCredentialOptionId = in.readString8();
}
@NonNull
@@ -136,15 +168,16 @@ public class CredentialEntry implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
dest.writeTypedObject(mSlice, flags);
- dest.writeTypedObject(mBeginGetCredentialOption, flags);
+ dest.writeString8(mBeginGetCredentialOptionId);
}
/**
- * Returns the request option for which this credential entry has been constructed.
+ * Returns the id of the {@link BeginGetCredentialOption} for which this credential
+ * entry has been constructed.
*/
@NonNull
- public BeginGetCredentialOption getBeginGetCredentialOption() {
- return mBeginGetCredentialOption;
+ public String getBeginGetCredentialOptionId() {
+ return mBeginGetCredentialOptionId;
}
/**
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index b5464db98d0e..fd9360f00d4a 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -34,40 +34,27 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.credentials.CredentialManager;
-import android.graphics.drawable.Drawable;
+import android.credentials.CredentialProviderInfo;
import android.os.Bundle;
import android.os.RemoteException;
-import android.text.TextUtils;
+import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
- * {@link ServiceInfo} and meta-data about a credential provider.
+ * {@link CredentialProviderInfo} generator.
*
* @hide
*/
-public final class CredentialProviderInfo {
- private static final String TAG = "CredentialProviderInfo";
-
- @NonNull
- private final ServiceInfo mServiceInfo;
- @NonNull
- private final List<String> mCapabilities;
-
- @NonNull
- private final Context mContext;
- @Nullable
- private final Drawable mIcon;
- @Nullable
- private final CharSequence mLabel;
- private final boolean mIsSystemProvider;
+public final class CredentialProviderInfoFactory {
+ private static final String TAG = "CredentialProviderInfoFactory";
/**
* Constructs an information instance of the credential provider.
@@ -79,14 +66,18 @@ public final class CredentialProviderInfo {
* @throws PackageManager.NameNotFoundException If provider service is not found
* @throws SecurityException If provider does not require the relevant permission
*/
- public CredentialProviderInfo(@NonNull Context context,
- @NonNull ComponentName serviceComponent, int userId, boolean isSystemProvider)
+ public static CredentialProviderInfo create(
+ @NonNull Context context,
+ @NonNull ComponentName serviceComponent,
+ int userId,
+ boolean isSystemProvider)
throws PackageManager.NameNotFoundException {
- this(
+ return create(
context,
getServiceInfoOrThrow(serviceComponent, userId),
isSystemProvider,
- /* disableSystemAppVerificationForTests= */ false);
+ /* disableSystemAppVerificationForTests= */ false,
+ /* isEnabled= */ false);
}
/**
@@ -98,13 +89,16 @@ public final class CredentialProviderInfo {
* @param isSystemProvider whether the provider app is a system provider
* @param disableSystemAppVerificationForTests whether to disable system app permission
* verification so that tests can install system providers
+ * @param isEnabled whether the user enabled this provider
* @throws SecurityException If provider does not require the relevant permission
*/
- public CredentialProviderInfo(
+ public static CredentialProviderInfo create(
@NonNull Context context,
@NonNull ServiceInfo serviceInfo,
boolean isSystemProvider,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ boolean isEnabled)
+ throws SecurityException {
verifyProviderPermission(serviceInfo);
if (isSystemProvider) {
if (!isValidSystemProvider(
@@ -114,23 +108,11 @@ public final class CredentialProviderInfo {
"Provider is not a valid system provider: " + serviceInfo);
}
}
- mIsSystemProvider = isSystemProvider;
- mContext = requireNonNull(context, "context must not be null");
- mServiceInfo = requireNonNull(serviceInfo, "serviceInfo must not be null");
- mCapabilities = new ArrayList<>();
- mIcon = mServiceInfo.loadIcon(mContext.getPackageManager());
- mLabel =
- mServiceInfo.loadSafeLabel(
- mContext.getPackageManager(),
- 0 /* do not ellipsize */,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
- Log.i(
- TAG,
- "mLabel is : "
- + mLabel
- + ", for: "
- + mServiceInfo.getComponentName().flattenToString());
- populateProviderCapabilities(context, serviceInfo);
+
+ return populateMetadata(context, serviceInfo)
+ .setSystemProvider(isSystemProvider)
+ .setEnabled(isEnabled)
+ .build();
}
private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
@@ -138,19 +120,14 @@ public final class CredentialProviderInfo {
if (permission.equals(serviceInfo.permission)) {
return;
}
-
- Slog.e(
- TAG,
- "Credential Provider Service from : "
- + serviceInfo.packageName
- + "does not require permission"
- + permission);
throw new SecurityException(
"Service does not require the expected permission : " + permission);
}
private static boolean isSystemProviderWithValidPermission(
ServiceInfo serviceInfo, Context context) {
+ requireNonNull(context, "context must not be null");
+
final String permission = Manifest.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE;
try {
ApplicationInfo appInfo =
@@ -177,67 +154,88 @@ public final class CredentialProviderInfo {
Context context,
ServiceInfo serviceInfo,
boolean disableSystemAppVerificationForTests) {
- boolean isValidSystemTestProvider =
- isTestSystemProvider(serviceInfo, disableSystemAppVerificationForTests);
- if (isValidSystemTestProvider) {
- return true;
+ requireNonNull(context, "context must not be null");
+
+ if (disableSystemAppVerificationForTests) {
+ Bundle metadata = serviceInfo.metaData;
+ if (metadata == null) {
+ Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo);
+ return false;
+ }
+ return metadata.getBoolean(
+ CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
}
+
return isSystemProviderWithValidPermission(serviceInfo, context);
}
- private static boolean isTestSystemProvider(
- ServiceInfo serviceInfo, boolean disableSystemAppVerificationForTests) {
- if (!disableSystemAppVerificationForTests) {
- return false;
- }
+ private static CredentialProviderInfo.Builder populateMetadata(
+ @NonNull Context context, ServiceInfo serviceInfo) {
+ requireNonNull(context, "context must not be null");
- Bundle metadata = serviceInfo.metaData;
+ final CredentialProviderInfo.Builder builder =
+ new CredentialProviderInfo.Builder(serviceInfo);
+ final PackageManager pm = context.getPackageManager();
+
+ // 1. Get the metadata for the service.
+ final Bundle metadata = serviceInfo.metaData;
if (metadata == null) {
- Slog.e(TAG, "metadata is null: " + serviceInfo);
- return false;
+ Log.i(TAG, "populateMetadata - metadata is null");
+ return builder;
}
- return metadata.getBoolean(CredentialProviderService.TEST_SYSTEM_PROVIDER_META_DATA_KEY);
- }
- private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) {
- final PackageManager pm = context.getPackageManager();
+ // 2. Extract the capabilities from the bundle.
try {
- Bundle metadata = serviceInfo.metaData;
Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
if (metadata == null || resources == null) {
- Log.i(TAG, "populateProviderCapabilities - metadata or resources is null");
- return;
- }
-
- String[] capabilities = resources.getStringArray(metadata.getInt(
- CredentialProviderService.CAPABILITY_META_DATA_KEY));
- if (capabilities == null || capabilities.length == 0) {
- Slog.i(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
- return;
+ Log.i(TAG, "populateMetadata - resources is null");
+ return builder;
}
- for (String capability : capabilities) {
- if (capability.isEmpty()) {
- Slog.i(TAG, "Skipping empty capability");
- continue;
- }
- Slog.i(TAG, "Capabilities found for provider: " + capability);
- mCapabilities.add(capability);
- }
+ builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo));
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, e.getMessage());
+ }
+
+ return builder;
+ }
+
+ private static List<String> populateProviderCapabilities(
+ Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
+ List<String> output = new ArrayList<>();
+ String[] capabilities = new String[0];
+
+ try {
+ capabilities =
+ resources.getStringArray(
+ metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
} catch (Resources.NotFoundException e) {
- Slog.e(TAG, e.getMessage());
+ Slog.e(TAG, "Failed to get capabilities: " + e.getMessage());
+ }
+
+ if (capabilities == null || capabilities.length == 0) {
+ Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
+ return output;
}
+
+ for (String capability : capabilities) {
+ if (capability.isEmpty()) {
+ Slog.e(TAG, "Skipping empty capability");
+ continue;
+ }
+ Slog.e(TAG, "Capabilities found for provider: " + capability);
+ output.add(capability);
+ }
+ return output;
}
- private static ServiceInfo getServiceInfoOrThrow(@NonNull ComponentName serviceComponent,
- int userId) throws PackageManager.NameNotFoundException {
+ private static ServiceInfo getServiceInfoOrThrow(
+ @NonNull ComponentName serviceComponent, int userId)
+ throws PackageManager.NameNotFoundException {
try {
- ServiceInfo si = AppGlobals.getPackageManager().getServiceInfo(
- serviceComponent,
- PackageManager.GET_META_DATA,
- userId);
+ ServiceInfo si =
+ AppGlobals.getPackageManager()
+ .getServiceInfo(serviceComponent, PackageManager.GET_META_DATA, userId);
if (si != null) {
return si;
}
@@ -256,6 +254,8 @@ public final class CredentialProviderInfo {
@NonNull Context context,
@UserIdInt int userId,
boolean disableSystemAppVerificationForTests) {
+ requireNonNull(context, "context must not be null");
+
final List<ServiceInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos = new ArrayList<>();
@@ -268,15 +268,20 @@ public final class CredentialProviderInfo {
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+ if (disableSystemAppVerificationForTests) {
+ if (serviceInfo != null) {
+ services.add(serviceInfo);
+ }
+ continue;
+ }
+
try {
- PackageManager.ApplicationInfoFlags appInfoFlags =
- disableSystemAppVerificationForTests
- ? PackageManager.ApplicationInfoFlags.of(0)
- : PackageManager.ApplicationInfoFlags.of(
- PackageManager.MATCH_SYSTEM_ONLY);
ApplicationInfo appInfo =
context.getPackageManager()
- .getApplicationInfo(serviceInfo.packageName, appInfoFlags);
+ .getApplicationInfo(
+ serviceInfo.packageName,
+ PackageManager.ApplicationInfoFlags.of(
+ PackageManager.MATCH_SYSTEM_ONLY));
if (appInfo == null || serviceInfo == null) {
continue;
@@ -300,19 +305,22 @@ public final class CredentialProviderInfo {
public static List<CredentialProviderInfo> getAvailableSystemServices(
@NonNull Context context,
@UserIdInt int userId,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
+
final List<CredentialProviderInfo> providerInfos = new ArrayList<>();
for (ServiceInfo si :
getAvailableSystemServiceInfos(
context, userId, disableSystemAppVerificationForTests)) {
try {
CredentialProviderInfo cpi =
- new CredentialProviderInfo(
+ CredentialProviderInfoFactory.create(
context,
si,
/* isSystemProvider= */ true,
- disableSystemAppVerificationForTests);
+ disableSystemAppVerificationForTests,
+ enabledServices.contains(si));
if (cpi.isSystemProvider()) {
providerInfos.add(cpi);
} else {
@@ -325,45 +333,12 @@ public final class CredentialProviderInfo {
return providerInfos;
}
- /**
- * Returns true if the service supports the given {@code credentialType}, false otherwise.
- */
- @NonNull
- public boolean hasCapability(@NonNull String credentialType) {
- return mCapabilities.contains(credentialType);
- }
-
- /** Returns the service info. */
- @NonNull
- public ServiceInfo getServiceInfo() {
- return mServiceInfo;
- }
-
- public boolean isSystemProvider() {
- return mIsSystemProvider;
- }
-
- /** Returns the service icon. */
- @Nullable
- public Drawable getServiceIcon() {
- return mIcon;
- }
-
- /** Returns the service label. */
- @Nullable
- public CharSequence getServiceLabel() {
- return mLabel;
- }
-
- /** Returns an immutable list of capabilities this provider service can support. */
- @NonNull
- public List<String> getCapabilities() {
- return Collections.unmodifiableList(mCapabilities);
- }
+ private static @Nullable PackagePolicy getDeviceManagerPolicy(
+ @NonNull Context context, int userId) {
+ Context newContext = context.createContextAsUser(UserHandle.of(userId), 0);
- private static @Nullable PackagePolicy getDeviceManagerPolicy(@NonNull Context context) {
try {
- DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class);
return dpm.getCredentialManagerPolicy();
} catch (SecurityException e) {
// If the current user is not enrolled in DPM then this can throw a security error.
@@ -381,21 +356,53 @@ public final class CredentialProviderInfo {
public static List<CredentialProviderInfo> getCredentialProviderServices(
@NonNull Context context,
int userId,
- boolean disableSystemAppVerificationForTests,
- int providerFilter) {
+ int providerFilter,
+ Set<ServiceInfo> enabledServices) {
+ requireNonNull(context, "context must not be null");
+
+ // Get the device policy.
+ PackagePolicy pp = getDeviceManagerPolicy(context, userId);
+
+ // Generate the provider list.
+ final boolean disableSystemAppVerificationForTests = false;
+ ProviderGenerator generator =
+ new ProviderGenerator(
+ context, pp, disableSystemAppVerificationForTests, providerFilter);
+ generator.addUserProviders(
+ getUserProviders(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
+ generator.addSystemProviders(
+ getAvailableSystemServices(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
+ return generator.getProviders();
+ }
+
+ /**
+ * Returns the valid credential provider services available for the user with the given {@code
+ * userId}. Includes test providers.
+ */
+ @NonNull
+ public static List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ @NonNull Context context,
+ int userId,
+ int providerFilter,
+ Set<ServiceInfo> enabledServices) {
requireNonNull(context, "context must not be null");
// Get the device policy.
- PackagePolicy pp = getDeviceManagerPolicy(context);
+ PackagePolicy pp = getDeviceManagerPolicy(context, userId);
// Generate the provider list.
+ final boolean disableSystemAppVerificationForTests = true;
ProviderGenerator generator =
new ProviderGenerator(
context, pp, disableSystemAppVerificationForTests, providerFilter);
generator.addUserProviders(
- getUserProviders(context, userId, disableSystemAppVerificationForTests));
+ getUserProviders(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
generator.addSystemProviders(
- getAvailableSystemServices(context, userId, disableSystemAppVerificationForTests));
+ getAvailableSystemServices(
+ context, userId, disableSystemAppVerificationForTests, enabledServices));
return generator.getProviders();
}
@@ -484,7 +491,8 @@ public final class CredentialProviderInfo {
private static List<CredentialProviderInfo> getUserProviders(
@NonNull Context context,
@UserIdInt int userId,
- boolean disableSystemAppVerificationForTests) {
+ boolean disableSystemAppVerificationForTests,
+ Set<ServiceInfo> enabledServices) {
final List<CredentialProviderInfo> services = new ArrayList<>();
final List<ResolveInfo> resolveInfos =
context.getPackageManager()
@@ -496,11 +504,12 @@ public final class CredentialProviderInfo {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
try {
CredentialProviderInfo cpi =
- new CredentialProviderInfo(
+ CredentialProviderInfoFactory.create(
context,
serviceInfo,
/* isSystemProvider= */ false,
- disableSystemAppVerificationForTests);
+ disableSystemAppVerificationForTests,
+ enabledServices.contains(serviceInfo));
if (!cpi.isSystemProvider()) {
services.add(cpi);
}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index d737f6b6cdc8..e88474d86798 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -18,6 +18,7 @@ package android.service.credentials;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import android.Manifest;
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
@@ -218,6 +219,11 @@ public abstract class CredentialProviderService extends Service {
GetCredentialException>() {
@Override
public void onResult(BeginGetCredentialResponse result) {
+ // If provider service does not possess the HYBRID permission, this
+ // check will throw an exception in the provider process.
+ if (result.getRemoteCredentialEntry() != null) {
+ enforceRemoteEntryPermission();
+ }
try {
callback.onSuccess(result);
} catch (RemoteException e) {
@@ -236,6 +242,15 @@ public abstract class CredentialProviderService extends Service {
));
return transport;
}
+ private void enforceRemoteEntryPermission() {
+ String permission =
+ Manifest.permission.PROVIDE_REMOTE_CREDENTIALS;
+ getApplicationContext().enforceCallingOrSelfPermission(
+ permission,
+ String.format("Provider must have %s, in order to set a "
+ + "remote entry", permission)
+ );
+ }
@Override
public ICancellationSignal onBeginCreateCredential(BeginCreateCredentialRequest request,
@@ -253,6 +268,11 @@ public abstract class CredentialProviderService extends Service {
BeginCreateCredentialResponse, CreateCredentialException>() {
@Override
public void onResult(BeginCreateCredentialResponse result) {
+ // If provider service does not possess the HYBRID permission, this
+ // check will throw an exception in the provider process.
+ if (result.getRemoteCreateEntry() != null) {
+ enforceRemoteEntryPermission();
+ }
try {
callback.onSuccess(result);
} catch (RemoteException e) {
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 94384b0c466d..f74f5332e8d3 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -533,7 +533,7 @@ public class TileService extends Service {
* the calling package or if the calling user cannot act on behalf of the user from the
* {@code context}.</li>
* <li> {@link IllegalArgumentException} if the user of the {@code context} is not the
- * current user.</li>
+ * current user. Only thrown for apps targeting {@link Build.VERSION_CODES#TIRAMISU}</li>
* </ul>
*/
public static final void requestListeningState(Context context, ComponentName component) {
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 0384454bb69c..d9ee859dc66b 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -72,7 +72,7 @@ import java.util.function.IntConsumer;
*/
@SystemApi
public abstract class HotwordDetectionService extends Service
- implements SandboxedDetectionServiceBase {
+ implements SandboxedDetectionInitializer {
private static final String TAG = "HotwordDetectionService";
private static final boolean DBG = false;
@@ -89,21 +89,23 @@ public abstract class HotwordDetectionService extends Service
/**
* Indicates that the updated status is successful.
*
- * @deprecated Replaced with {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS}
+ * @deprecated Replaced with
+ * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_SUCCESS}
*/
@Deprecated
public static final int INITIALIZATION_STATUS_SUCCESS =
- SandboxedDetectionServiceBase.INITIALIZATION_STATUS_SUCCESS;
+ SandboxedDetectionInitializer.INITIALIZATION_STATUS_SUCCESS;
/**
* Indicates that the callback wasn’t invoked within the timeout.
* This is used by system.
*
- * @deprecated Replaced with {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_UNKNOWN}
+ * @deprecated Replaced with
+ * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_UNKNOWN}
*/
@Deprecated
public static final int INITIALIZATION_STATUS_UNKNOWN =
- SandboxedDetectionServiceBase.INITIALIZATION_STATUS_UNKNOWN;
+ SandboxedDetectionInitializer.INITIALIZATION_STATUS_UNKNOWN;
/**
* Source for the given audio stream.
@@ -259,7 +261,7 @@ public abstract class HotwordDetectionService extends Service
*
* @hide
* @deprecated Replaced with
- * {@link SandboxedDetectionServiceBase#getMaxCustomInitializationStatus()}
+ * {@link SandboxedDetectionInitializer#getMaxCustomInitializationStatus()}
*/
@SystemApi
@Deprecated
@@ -368,7 +370,7 @@ public abstract class HotwordDetectionService extends Service
private void onUpdateStateInternal(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IRemoteCallback callback) {
IntConsumer intConsumer =
- SandboxedDetectionServiceBase.createInitializationStatusConsumer(callback);
+ SandboxedDetectionInitializer.createInitializationStatusConsumer(callback);
onUpdateState(options, sharedMemory, UPDATE_TIMEOUT_MILLIS, intConsumer);
}
diff --git a/core/java/android/service/voice/HotwordDetector.java b/core/java/android/service/voice/HotwordDetector.java
index 22d97b7bbcaa..93fcec14cf1c 100644
--- a/core/java/android/service/voice/HotwordDetector.java
+++ b/core/java/android/service/voice/HotwordDetector.java
@@ -283,9 +283,9 @@ public interface HotwordDetector {
*
* @param status Info about initialization state of {@link HotwordDetectionService} or
* {@link VisualQueryDetectionService}; allowed values are
- * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
- * 1<->{@link SandboxedDetectionServiceBase#getMaxCustomInitializationStatus()},
- * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_UNKNOWN}.
+ * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_SUCCESS},
+ * 1<->{@link SandboxedDetectionInitializer#getMaxCustomInitializationStatus()},
+ * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_UNKNOWN}.
*/
void onHotwordDetectionServiceInitialized(int status);
diff --git a/core/java/android/service/voice/SandboxedDetectionServiceBase.java b/core/java/android/service/voice/SandboxedDetectionInitializer.java
index 43331642bd1e..4a41968395a7 100644
--- a/core/java/android/service/voice/SandboxedDetectionServiceBase.java
+++ b/core/java/android/service/voice/SandboxedDetectionInitializer.java
@@ -28,12 +28,12 @@ import android.os.SharedMemory;
import java.util.function.IntConsumer;
/**
- * Base for all sandboxed detection services, providing a common interface for initialization.
+ * Provides common initialzation methods for sandboxed detection services.
*
* @hide
*/
@SystemApi
-public interface SandboxedDetectionServiceBase {
+public interface SandboxedDetectionInitializer {
/**
* Indicates that the updated status is successful.
@@ -77,7 +77,7 @@ public interface SandboxedDetectionServiceBase {
if (callback != null) {
intConsumer =
value -> {
- if (value > SandboxedDetectionServiceBase
+ if (value > SandboxedDetectionInitializer
.getMaxCustomInitializationStatus()) {
throw new IllegalArgumentException(
"The initialization status is invalid for " + value);
diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java
index 1783186cd045..cbe7666ddf43 100644
--- a/core/java/android/service/voice/VisualQueryDetectionService.java
+++ b/core/java/android/service/voice/VisualQueryDetectionService.java
@@ -20,9 +20,11 @@ import android.annotation.DurationMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.ContentCaptureOptions;
+import android.content.Context;
import android.content.Intent;
import android.hardware.soundtrigger.SoundTrigger;
import android.media.AudioFormat;
@@ -35,6 +37,7 @@ import android.os.RemoteException;
import android.os.SharedMemory;
import android.speech.IRecognitionServiceManager;
import android.util.Log;
+import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.IContentCaptureManager;
import java.util.Objects;
@@ -58,7 +61,7 @@ import java.util.function.IntConsumer;
*/
@SystemApi
public abstract class VisualQueryDetectionService extends Service
- implements SandboxedDetectionServiceBase {
+ implements SandboxedDetectionInitializer {
private static final String TAG = VisualQueryDetectionService.class.getSimpleName();
@@ -79,6 +82,10 @@ public abstract class VisualQueryDetectionService extends Service
public static final String KEY_INITIALIZATION_STATUS = "initialization_status";
private IDetectorSessionVisualQueryDetectionCallback mRemoteCallback = null;
+ @Nullable
+ private ContentCaptureManager mContentCaptureManager;
+ @Nullable
+ private IRecognitionServiceManager mIRecognitionServiceManager;
private final ISandboxedDetectionService mInterface = new ISandboxedDetectionService.Stub() {
@@ -139,15 +146,29 @@ public abstract class VisualQueryDetectionService extends Service
@Override
public void updateContentCaptureManager(IContentCaptureManager manager,
ContentCaptureOptions options) {
- Log.v(TAG, "Ignore #updateContentCaptureManager");
+ mContentCaptureManager = new ContentCaptureManager(
+ VisualQueryDetectionService.this, manager, options);
}
@Override
public void updateRecognitionServiceManager(IRecognitionServiceManager manager) {
- Log.v(TAG, "Ignore #updateRecognitionServiceManager");
+ mIRecognitionServiceManager = manager;
}
};
+ @Override
+ @SuppressLint("OnNameExpected")
+ public @Nullable Object getSystemService(@ServiceName @NonNull String name) {
+ if (Context.CONTENT_CAPTURE_MANAGER_SERVICE.equals(name)) {
+ return mContentCaptureManager;
+ } else if (Context.SPEECH_RECOGNITION_SERVICE.equals(name)
+ && mIRecognitionServiceManager != null) {
+ return mIRecognitionServiceManager.asBinder();
+ } else {
+ return super.getSystemService(name);
+ }
+ }
+
/**
* {@inheritDoc}
* @hide
@@ -175,7 +196,7 @@ public abstract class VisualQueryDetectionService extends Service
private void onUpdateStateInternal(@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory, IRemoteCallback callback) {
IntConsumer intConsumer =
- SandboxedDetectionServiceBase.createInitializationStatusConsumer(callback);
+ SandboxedDetectionInitializer.createInitializationStatusConsumer(callback);
onUpdateState(options, sharedMemory, UPDATE_TIMEOUT_MILLIS, intConsumer);
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index f0f6a4f829cf..7dc0687b549d 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -201,9 +201,10 @@ public class VisualQueryDetector {
* short amount of time to report its initialization state.
*
* @param status Info about initialization state of {@link VisualQueryDetectionService}; the
- * allowed values are {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_SUCCESS},
- * 1<->{@link SandboxedDetectionServiceBase#getMaxCustomInitializationStatus()},
- * {@link SandboxedDetectionServiceBase#INITIALIZATION_STATUS_UNKNOWN}.
+ * allowed values are
+ * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_SUCCESS},
+ * 1<->{@link SandboxedDetectionInitializer#getMaxCustomInitializationStatus()},
+ * {@link SandboxedDetectionInitializer#INITIALIZATION_STATUS_UNKNOWN}.
*/
void onVisualQueryDetectionServiceInitialized(int status);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 26bc5d12d3b9..259012f5eb30 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -60,7 +60,6 @@ import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -181,9 +180,6 @@ public abstract class WallpaperService extends Service {
private final ArrayMap<IBinder, IWallpaperEngineWrapper> mActiveEngines = new ArrayMap<>();
- private Handler mBackgroundHandler;
- private HandlerThread mBackgroundThread;
-
static final class WallpaperCommand {
String action;
int x;
@@ -202,6 +198,14 @@ public abstract class WallpaperService extends Service {
*/
public class Engine {
IWallpaperEngineWrapper mIWallpaperEngine;
+ final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
+ final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
+
+ // 2D matrix [x][y] to represent a page of a portion of a window
+ EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
+ Bitmap mLastScreenshot;
+ int mLastWindowPage = -1;
+ private boolean mResetWindowPages;
// Copies from mIWallpaperEngine.
HandlerCaller mCaller;
@@ -262,27 +266,11 @@ public abstract class WallpaperService extends Service {
final Object mLock = new Object();
boolean mOffsetMessageEnqueued;
-
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
float mPendingXOffset;
float mPendingYOffset;
float mPendingXOffsetStep;
float mPendingYOffsetStep;
-
- /**
- * local color extraction related fields
- * to be used by the background thread only (except the atomic boolean)
- */
- final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4);
- final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4);
- private long mLastProcessLocalColorsTimestamp;
- private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false);
- private int mPixelCopyCount = 0;
- // 2D matrix [x][y] to represent a page of a portion of a window
- EngineWindowPage[] mWindowPages = new EngineWindowPage[0];
- Bitmap mLastScreenshot;
- private boolean mResetWindowPages;
-
boolean mPendingSync;
MotionEvent mPendingMove;
boolean mIsInAmbientMode;
@@ -291,8 +279,12 @@ public abstract class WallpaperService extends Service {
private long mLastColorInvalidation;
private final Runnable mNotifyColorsChanged = this::notifyColorsChanged;
+ // used to throttle processLocalColors
+ private long mLastProcessLocalColorsTimestamp;
+ private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false);
private final Supplier<Long> mClockFunction;
private final Handler mHandler;
+
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
@@ -862,7 +854,7 @@ public abstract class WallpaperService extends Service {
+ "was not established.");
}
mResetWindowPages = true;
- processLocalColors();
+ processLocalColors(mPendingXOffset, mPendingXOffsetStep);
} catch (RemoteException e) {
Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e);
}
@@ -1400,7 +1392,7 @@ public abstract class WallpaperService extends Service {
resetWindowPages();
mSession.finishDrawing(mWindow, null /* postDrawTransaction */,
Integer.MAX_VALUE);
- processLocalColors();
+ processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
reposition();
reportEngineShown(shouldWaitForEngineShown());
@@ -1544,7 +1536,7 @@ public abstract class WallpaperService extends Service {
if (!mDestroyed) {
mVisible = visible;
reportVisibility(false);
- if (mReportedVisible) processLocalColors();
+ if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep);
} else {
AnimationHandler.requestAnimatorsEnabled(visible, this);
}
@@ -1647,41 +1639,31 @@ public abstract class WallpaperService extends Service {
}
// setup local color extraction data
- processLocalColors();
+ processLocalColors(xOffset, xOffsetStep);
}
/**
* Thread-safe util to call {@link #processLocalColorsInternal} with a minimum interval of
* {@link #PROCESS_LOCAL_COLORS_INTERVAL_MS} between two calls.
*/
- private void processLocalColors() {
+ private void processLocalColors(float xOffset, float xOffsetStep) {
if (mProcessLocalColorsPending.compareAndSet(false, true)) {
final long now = mClockFunction.get();
final long timeSinceLastColorProcess = now - mLastProcessLocalColorsTimestamp;
final long timeToWait = Math.max(0,
PROCESS_LOCAL_COLORS_INTERVAL_MS - timeSinceLastColorProcess);
- mBackgroundHandler.postDelayed(() -> {
+ mHandler.postDelayed(() -> {
mLastProcessLocalColorsTimestamp = now + timeToWait;
mProcessLocalColorsPending.set(false);
- processLocalColorsInternal();
+ processLocalColorsInternal(xOffset, xOffsetStep);
}, timeToWait);
}
}
- private void processLocalColorsInternal() {
+ private void processLocalColorsInternal(float xOffset, float xOffsetStep) {
// implemented by the wallpaper
if (supportsLocalColorExtraction()) return;
- assertBackgroundThread();
- float xOffset;
- float xOffsetStep;
- float wallpaperDimAmount;
- synchronized (mLock) {
- xOffset = mPendingXOffset;
- xOffsetStep = mPendingXOffsetStep;
- wallpaperDimAmount = mWallpaperDimAmount;
- }
-
if (DEBUG) {
Log.d(TAG, "processLocalColors " + xOffset + " of step "
+ xOffsetStep);
@@ -1744,7 +1726,7 @@ public abstract class WallpaperService extends Service {
xPage = mWindowPages.length - 1;
}
current = mWindowPages[xPage];
- updatePage(current, xPage, xPages, wallpaperDimAmount);
+ updatePage(current, xPage, xPages, finalXOffsetStep);
Trace.endSection();
}
@@ -1764,23 +1746,16 @@ public abstract class WallpaperService extends Service {
}
}
- /**
- * Must be called with the surface lock held.
- * Must not be called if the surface is not valid.
- * Will unlock the surface when done using it.
- */
void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages,
- float wallpaperDimAmount) {
-
- assertBackgroundThread();
-
+ float xOffsetStep) {
// in case the clock is zero, we start with negative time
long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION;
long lapsed = current - currentPage.getLastUpdateTime();
// Always update the page when the last update time is <= 0
// This is important especially when the device first boots
- if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) return;
-
+ if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) {
+ return;
+ }
Surface surface = mSurfaceHolder.getSurface();
if (!surface.isValid()) return;
boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y;
@@ -1796,42 +1771,33 @@ public abstract class WallpaperService extends Service {
Bitmap screenShot = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
final Bitmap finalScreenShot = screenShot;
- final String pixelCopySectionName = "WallpaperService#pixelCopy";
- final int pixelCopyCount = mPixelCopyCount++;
- Trace.beginAsyncSection(pixelCopySectionName, pixelCopyCount);
- try {
- PixelCopy.request(surface, screenShot, (res) -> {
- Trace.endAsyncSection(pixelCopySectionName, pixelCopyCount);
- if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
- if (res != PixelCopy.SUCCESS) {
- Bitmap lastBitmap = currentPage.getBitmap();
- // assign the last bitmap taken for now
- currentPage.setBitmap(mLastScreenshot);
- Bitmap lastScreenshot = mLastScreenshot;
- if (lastScreenshot != null && !lastScreenshot.isRecycled()
- && !Objects.equals(lastBitmap, lastScreenshot)) {
- updatePageColors(currentPage, pageIndx, numPages, wallpaperDimAmount);
- }
- } else {
- mLastScreenshot = finalScreenShot;
- // going to hold this lock for a while
- currentPage.setBitmap(finalScreenShot);
- currentPage.setLastUpdateTime(current);
- updatePageColors(currentPage, pageIndx, numPages, wallpaperDimAmount);
+ Trace.beginSection("WallpaperService#pixelCopy");
+ PixelCopy.request(surface, screenShot, (res) -> {
+ Trace.endSection();
+ if (DEBUG) Log.d(TAG, "result of pixel copy is " + res);
+ if (res != PixelCopy.SUCCESS) {
+ Bitmap lastBitmap = currentPage.getBitmap();
+ // assign the last bitmap taken for now
+ currentPage.setBitmap(mLastScreenshot);
+ Bitmap lastScreenshot = mLastScreenshot;
+ if (lastScreenshot != null && !lastScreenshot.isRecycled()
+ && !Objects.equals(lastBitmap, lastScreenshot)) {
+ updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
}
- }, mBackgroundHandler);
- } catch (IllegalArgumentException e) {
- // this can potentially happen if the surface is invalidated right between the
- // surface.isValid() check and the PixelCopy operation.
- // in this case, stop: we'll compute colors on the next processLocalColors call.
- Log.i(TAG, "Cancelling processLocalColors: exception caught during PixelCopy");
- }
+ } else {
+ mLastScreenshot = finalScreenShot;
+ // going to hold this lock for a while
+ currentPage.setBitmap(finalScreenShot);
+ currentPage.setLastUpdateTime(current);
+ updatePageColors(currentPage, pageIndx, numPages, xOffsetStep);
+ }
+ }, mHandler);
+
}
// locked by the passed page
- private void updatePageColors(
- EngineWindowPage page, int pageIndx, int numPages, float wallpaperDimAmount) {
+ private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages,
+ float xOffsetStep) {
if (page.getBitmap() == null) return;
- assertBackgroundThread();
Trace.beginSection("WallpaperService#updatePageColors");
if (DEBUG) {
Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas "
@@ -1853,7 +1819,7 @@ public abstract class WallpaperService extends Service {
Log.e(TAG, "Error creating page local color bitmap", e);
continue;
}
- WallpaperColors color = WallpaperColors.fromBitmap(target, wallpaperDimAmount);
+ WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount);
target.recycle();
WallpaperColors currentColor = page.getColors(area);
@@ -1870,26 +1836,17 @@ public abstract class WallpaperService extends Service {
+ " local color callback for area" + area + " for page " + pageIndx
+ " of " + numPages);
}
- mHandler.post(() -> {
- try {
- mConnection.onLocalWallpaperColorsChanged(area, color,
- mDisplayContext.getDisplayId());
- } catch (RemoteException e) {
- Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
- }
- });
+ try {
+ mConnection.onLocalWallpaperColorsChanged(area, color,
+ mDisplayContext.getDisplayId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e);
+ }
}
}
Trace.endSection();
}
- private void assertBackgroundThread() {
- if (!mBackgroundHandler.getLooper().isCurrentThread()) {
- throw new IllegalStateException(
- "ProcessLocalColors should be called from the background thread");
- }
- }
-
private RectF generateSubRect(RectF in, int pageInx, int numPages) {
float minLeft = (float) (pageInx) / (float) (numPages);
float maxRight = (float) (pageInx + 1) / (float) (numPages);
@@ -1914,6 +1871,7 @@ public abstract class WallpaperService extends Service {
if (supportsLocalColorExtraction()) return;
if (!mResetWindowPages) return;
mResetWindowPages = false;
+ mLastWindowPage = -1;
for (int i = 0; i < mWindowPages.length; i++) {
mWindowPages[i].setLastUpdateTime(0L);
}
@@ -1939,10 +1897,12 @@ public abstract class WallpaperService extends Service {
if (DEBUG) {
Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions);
}
- mBackgroundHandler.post(() -> {
+ mHandler.post(() -> {
mLocalColorsToAdd.addAll(regions);
- processLocalColors();
+ processLocalColors(mPendingXOffset, mPendingYOffset);
});
+
+
}
/**
@@ -1952,7 +1912,7 @@ public abstract class WallpaperService extends Service {
*/
public void removeLocalColorsAreas(@NonNull List<RectF> regions) {
if (supportsLocalColorExtraction()) return;
- mBackgroundHandler.post(() -> {
+ mHandler.post(() -> {
float step = mPendingXOffsetStep;
mLocalColorsToAdd.removeAll(regions);
mLocalColorAreas.removeAll(regions);
@@ -2618,9 +2578,6 @@ public abstract class WallpaperService extends Service {
@Override
public void onCreate() {
Trace.beginSection("WPMS.onCreate");
- mBackgroundThread = new HandlerThread("DefaultWallpaperLocalColorExtractor");
- mBackgroundThread.start();
- mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
super.onCreate();
Trace.endSection();
}
@@ -2633,7 +2590,6 @@ public abstract class WallpaperService extends Service {
engineWrapper.destroy();
}
mActiveEngines.clear();
- mBackgroundThread.quitSafely();
Trace.endSection();
}
diff --git a/core/java/android/text/GraphemeClusterSegmentFinder.java b/core/java/android/text/GraphemeClusterSegmentFinder.java
index 656774f66792..0f6fdaf23c65 100644
--- a/core/java/android/text/GraphemeClusterSegmentFinder.java
+++ b/core/java/android/text/GraphemeClusterSegmentFinder.java
@@ -18,7 +18,8 @@ package android.text;
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.graphics.Paint;
+import android.graphics.TemporaryBuffer;
+import android.graphics.text.GraphemeBreak;
/**
* Implementation of {@code SegmentFinder} using grapheme clusters as the text segment. Whitespace
@@ -31,8 +32,8 @@ import android.graphics.Paint;
* Segmentation - Grapheme Cluster Boundaries</a>
*/
public class GraphemeClusterSegmentFinder extends SegmentFinder {
- private final CharSequence mText;
- private final TextPaint mTextPaint;
+ private static AutoGrowArray.FloatArray sTempAdvances = null;
+ private final boolean[] mIsGraphemeBreak;
/**
* Constructs a GraphemeClusterSegmentFinder instance for the specified text which uses the
@@ -43,51 +44,73 @@ public class GraphemeClusterSegmentFinder extends SegmentFinder {
*/
public GraphemeClusterSegmentFinder(
@NonNull CharSequence text, @NonNull TextPaint textPaint) {
- mText = text;
- mTextPaint = textPaint;
+
+ if (sTempAdvances == null) {
+ sTempAdvances = new AutoGrowArray.FloatArray(text.length());
+ } else if (sTempAdvances.size() < text.length()) {
+ sTempAdvances.resize(text.length());
+ }
+
+ mIsGraphemeBreak = new boolean[text.length()];
+ float[] advances = sTempAdvances.getRawArray();
+ char[] chars = TemporaryBuffer.obtain(text.length());
+
+ TextUtils.getChars(text, 0, text.length(), chars, 0);
+
+ textPaint.getTextWidths(chars, 0, text.length(), advances);
+
+ GraphemeBreak.isGraphemeBreak(advances, chars, /* start= */ 0, /* end= */ text.length(),
+ mIsGraphemeBreak);
+ TemporaryBuffer.recycle(chars);
+ }
+
+ private int previousBoundary(@IntRange(from = 0) int offset) {
+ if (offset <= 0) return DONE;
+ do {
+ --offset;
+ } while (offset > 0 && !mIsGraphemeBreak[offset]);
+ return offset;
+ }
+
+ private int nextBoundary(@IntRange(from = 0) int offset) {
+ if (offset >= mIsGraphemeBreak.length) return DONE;
+ do {
+ ++offset;
+ } while (offset < mIsGraphemeBreak.length && !mIsGraphemeBreak[offset]);
+ return offset;
}
@Override
public int previousStartBoundary(@IntRange(from = 0) int offset) {
- if (offset == 0) return DONE;
- int boundary = mTextPaint.getTextRunCursor(
- mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE);
- return boundary == -1 ? DONE : boundary;
+ return previousBoundary(offset);
}
@Override
public int previousEndBoundary(@IntRange(from = 0) int offset) {
if (offset == 0) return DONE;
- int boundary = mTextPaint.getTextRunCursor(
- mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE);
+ int boundary = previousBoundary(offset);
// Check that there is another cursor position before, otherwise this is not a valid
// end boundary.
- if (mTextPaint.getTextRunCursor(
- mText, 0, mText.length(), false, boundary, Paint.CURSOR_BEFORE) == -1) {
+ if (boundary == DONE || previousBoundary(boundary) == DONE) {
return DONE;
}
- return boundary == -1 ? DONE : boundary;
+ return boundary;
}
@Override
public int nextStartBoundary(@IntRange(from = 0) int offset) {
- if (offset == mText.length()) return DONE;
- int boundary = mTextPaint.getTextRunCursor(
- mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER);
+ if (offset == mIsGraphemeBreak.length) return DONE;
+ int boundary = nextBoundary(offset);
// Check that there is another cursor position after, otherwise this is not a valid
// start boundary.
- if (mTextPaint.getTextRunCursor(
- mText, 0, mText.length(), false, boundary, Paint.CURSOR_AFTER) == -1) {
+ if (boundary == DONE || nextBoundary(boundary) == DONE) {
return DONE;
}
- return boundary == -1 ? DONE : boundary;
+ return boundary;
}
@Override
public int nextEndBoundary(@IntRange(from = 0) int offset) {
- if (offset == mText.length()) return DONE;
- int boundary = mTextPaint.getTextRunCursor(
- mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER);
- return boundary == -1 ? DONE : boundary;
+ return nextBoundary(offset);
}
}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2ae882c81b50..6201b3a91eff 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -220,9 +220,9 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
- DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
+ DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 07ac5977c4e3..b4675e0127de 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -81,7 +81,10 @@ public abstract class DisplayEventReceiver {
// GC'd while the native peer of the receiver is using them.
private MessageQueue mMessageQueue;
+ private final VsyncEventData mVsyncEventData = new VsyncEventData();
+
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
+ WeakReference<VsyncEventData> vsyncEventData,
MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle);
private static native long nativeGetDisplayEventReceiverFinalizer();
@FastNative
@@ -124,7 +127,9 @@ public abstract class DisplayEventReceiver {
}
mMessageQueue = looper.getQueue();
- mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
+ mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this),
+ new WeakReference<VsyncEventData>(mVsyncEventData),
+ mMessageQueue,
vsyncSource, eventRegistration, layerHandle);
mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this,
mReceiverPtr);
@@ -142,9 +147,6 @@ public abstract class DisplayEventReceiver {
}
static final class VsyncEventData {
-
- static final FrameTimeline[] INVALID_FRAME_TIMELINES =
- {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)};
// The amount of frame timeline choices.
// Must be in sync with VsyncEventData::kFrameTimelinesLength in
// frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime
@@ -152,6 +154,10 @@ public abstract class DisplayEventReceiver {
static final int FRAME_TIMELINES_LENGTH = 7;
public static class FrameTimeline {
+ FrameTimeline() {}
+
+ // Called from native code.
+ @SuppressWarnings("unused")
FrameTimeline(long vsyncId, long expectedPresentationTime, long deadline) {
this.vsyncId = vsyncId;
this.expectedPresentationTime = expectedPresentationTime;
@@ -160,14 +166,14 @@ public abstract class DisplayEventReceiver {
// The frame timeline vsync id, used to correlate a frame
// produced by HWUI with the timeline data stored in Surface Flinger.
- public final long vsyncId;
+ public long vsyncId = FrameInfo.INVALID_VSYNC_ID;
// The frame timestamp for when the frame is expected to be presented.
- public final long expectedPresentationTime;
+ public long expectedPresentationTime = Long.MAX_VALUE;
// The frame deadline timestamp in {@link System#nanoTime()} timebase that it is
// allotted for the frame to be completed.
- public final long deadline;
+ public long deadline = Long.MAX_VALUE;
}
/**
@@ -175,11 +181,18 @@ public abstract class DisplayEventReceiver {
* {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily
* delayed by the app.
*/
- public final long frameInterval;
+ public long frameInterval = -1;
public final FrameTimeline[] frameTimelines;
- public final int preferredFrameTimelineIndex;
+ public int preferredFrameTimelineIndex = 0;
+
+ VsyncEventData() {
+ frameTimelines = new FrameTimeline[FRAME_TIMELINES_LENGTH];
+ for (int i = 0; i < frameTimelines.length; i++) {
+ frameTimelines[i] = new FrameTimeline();
+ }
+ }
// Called from native code.
@SuppressWarnings("unused")
@@ -190,12 +203,6 @@ public abstract class DisplayEventReceiver {
this.frameInterval = frameInterval;
}
- VsyncEventData() {
- this.frameInterval = -1;
- this.frameTimelines = INVALID_FRAME_TIMELINES;
- this.preferredFrameTimelineIndex = 0;
- }
-
public FrameTimeline preferredFrameTimeline() {
return frameTimelines[preferredFrameTimelineIndex];
}
@@ -299,9 +306,8 @@ public abstract class DisplayEventReceiver {
// Called from native code.
@SuppressWarnings("unused")
- private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
- VsyncEventData vsyncEventData) {
- onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);
+ private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
+ onVsync(timestampNanos, physicalDisplayId, frame, mVsyncEventData);
}
// Called from native code.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 406f446156a9..24a0355dd10e 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -244,6 +244,10 @@ public final class InputWindowHandle {
window = iwindow;
}
+ public @Nullable IBinder getWindowToken() {
+ return windowToken;
+ }
+
public IWindow getWindow() {
if (window != null) {
return window;
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
index 4d32efea9081..7d452f954fb0 100644
--- a/core/java/android/view/MotionPredictor.java
+++ b/core/java/android/view/MotionPredictor.java
@@ -17,14 +17,11 @@
package android.view;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import libcore.util.NativeAllocationRegistry;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
/**
* Calculate motion predictions.
*
@@ -68,9 +65,12 @@ public final class MotionPredictor {
/**
* Record a movement so that in the future, a prediction for the current gesture can be
- * generated. Ensure to add all motions from the gesture of interest to generate correct
- * predictions.
+ * generated. Only gestures from one input device at a time should be provided to an instance of
+ * MotionPredictor.
+ *
* @param event The received event
+ *
+ * @throws IllegalArgumentException if an inconsistent MotionEvent stream is sent.
*/
public void record(@NonNull MotionEvent event) {
if (!isPredictionEnabled()) {
@@ -80,28 +80,24 @@ public final class MotionPredictor {
}
/**
- * Get predicted events for all gestures that have been provided to {@link #record}.
- * If events from multiple devices were sent to 'record', this will produce a separate
- * {@link MotionEvent} for each device. The returned list may be empty if no predictions for
- * any of the added events/devices are available.
+ * Get a predicted event for the gesture that has been provided to {@link #record}.
* Predictions may not reach the requested timestamp if the confidence in the prediction results
* is low.
*
* @param predictionTimeNanos The time that the prediction should target, in the
* {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds.
*
- * @return A list of predicted motion events, with at most one for each device observed by
- * {@link #record}. Be sure to check the historical data in addition to the latest
- * ({@link MotionEvent#getX getX()}, {@link MotionEvent#getY getY()}) coordinates for smooth
- * prediction curves. An empty list is returned if predictions are not supported, or not
- * possible for the current set of gestures.
+ * @return The predicted motion event, or `null` if predictions are not supported, or not
+ * possible for the current gesture. Be sure to check the historical data in addition to the
+ * latest ({@link MotionEvent#getX getX()}, {@link MotionEvent#getY getY()}) coordinates for
+ * smooth prediction curves.
*/
- @NonNull
- public List<MotionEvent> predict(long predictionTimeNanos) {
+ @Nullable
+ public MotionEvent predict(long predictionTimeNanos) {
if (!isPredictionEnabled()) {
- return Collections.emptyList();
+ return null;
}
- return Arrays.asList(nativePredict(mPtr, predictionTimeNanos));
+ return nativePredict(mPtr, predictionTimeNanos);
}
private boolean isPredictionEnabled() {
@@ -129,7 +125,7 @@ public final class MotionPredictor {
private static native long nativeInitialize(int offsetNanos);
private static native void nativeRecord(long nativePtr, MotionEvent event);
- private static native MotionEvent[] nativePredict(long nativePtr, long predictionTimeNanos);
+ private static native MotionEvent nativePredict(long nativePtr, long predictionTimeNanos);
private static native boolean nativeIsPredictionAvailable(long nativePtr, int deviceId,
int source);
private static native long nativeGetNativeMotionPredictorFinalizer();
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 85aea85907b5..4a7ed644f9e2 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -32,9 +32,10 @@ import java.util.Map;
*
* Use {@link #obtain} to retrieve a new instance of the class when you are going
* to begin tracking. Put the motion events you receive into it with
- * {@link #addMovement(MotionEvent)}. When you want to determine the velocity call
- * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
- * and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.
+ * {@link #addMovement(MotionEvent)}. When you want to determine the velocity, call
+ * {@link #computeCurrentVelocity(int)} and then call the velocity-getter methods like
+ * {@link #getXVelocity(int)}, {@link #getYVelocity(int)}, or {@link #getAxisVelocity(int, int)}
+ * to retrieve velocity for different axes and/or pointer IDs.
*/
public final class VelocityTracker {
private static final SynchronizedPool<VelocityTracker> sPool =
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 869efc69872e..8bf32324dd5a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8920,7 +8920,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;
}
@@ -8928,6 +8929,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);
}
/**
@@ -16159,7 +16163,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;
@@ -26375,6 +26380,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 807af5b54081..a8b4da14c1c5 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;
@@ -79,6 +80,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;
@@ -92,12 +94,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;
@@ -895,6 +899,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;
private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
@@ -986,6 +999,8 @@ public final class ViewRootImpl implements ViewParent,
mViewConfiguration,
mContext.getSystemService(InputMethodManager.class));
+ mViewBoundsSandboxingEnabled = getViewBoundsSandboxingEnabled();
+
String processorOverrideName = context.getResources().getString(
R.string.config_inputEventCompatProcessorOverrideClassName);
if (processorOverrideName.isEmpty()) {
@@ -8598,6 +8613,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);
}
/**
@@ -8614,6 +8632,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/Window.java b/core/java/android/view/Window.java
index a208cb3de8aa..21fe87f42e1b 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -823,6 +823,11 @@ public abstract class Window {
/** @hide */
public final void destroy() {
mDestroyed = true;
+ onDestroy();
+ }
+
+ /** @hide */
+ protected void onDestroy() {
}
/** @hide */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f8636788ee3c..35ed88fc420e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -454,6 +454,11 @@ public interface WindowManager extends ViewManager {
*/
int TRANSIT_WAKE = 11;
/**
+ * The screen is turning off. This is used as a message to stop all animations.
+ * @hide
+ */
+ int TRANSIT_SLEEP = 12;
+ /**
* The first slot for custom transition types. Callers (like Shell) can make use of custom
* transition types for dealing with special cases. These types are effectively ignored by
* Core and will just be passed along as part of TransitionInfo objects. An example is
@@ -462,7 +467,7 @@ public interface WindowManager extends ViewManager {
* implementation.
* @hide
*/
- int TRANSIT_FIRST_CUSTOM = 12;
+ int TRANSIT_FIRST_CUSTOM = 13;
/**
* @hide
@@ -480,6 +485,7 @@ public interface WindowManager extends ViewManager {
TRANSIT_KEYGUARD_UNOCCLUDE,
TRANSIT_PIP,
TRANSIT_WAKE,
+ TRANSIT_SLEEP,
TRANSIT_FIRST_CUSTOM
})
@Retention(RetentionPolicy.SOURCE)
@@ -866,6 +872,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
@@ -1437,6 +1479,7 @@ public interface WindowManager extends ViewManager {
case TRANSIT_KEYGUARD_UNOCCLUDE: return "KEYGUARD_UNOCCLUDE";
case TRANSIT_PIP: return "PIP";
case TRANSIT_WAKE: return "WAKE";
+ case TRANSIT_SLEEP: return "SLEEP";
case TRANSIT_FIRST_CUSTOM: return "FIRST_CUSTOM";
default:
if (type > TRANSIT_FIRST_CUSTOM) {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index aef0e651ff8d..ab9f492694f5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -301,6 +301,14 @@ public final class AutofillManager {
public static final String EXTRA_AUGMENTED_AUTOFILL_CLIENT =
"android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT";
+ /**
+ * Autofill Hint to indicate that it can match any field.
+ *
+ * @hide
+ */
+ @TestApi
+ public static final String ANY_HINT = "any";
+
private static final String SESSION_ID_TAG = "android:sessionId";
private static final String STATE_TAG = "android:state";
private static final String LAST_AUTOFILLED_DATA_TAG = "android:lastAutoFilledData";
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 6ddfcb825acc..8cff06641847 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -562,11 +562,11 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
final int numberEvents = mEvents.size();
final String reasonString = getFlushReasonAsString(reason);
- if (sDebug) {
+ if (sVerbose) {
ContentCaptureEvent event = mEvents.get(numberEvents - 1);
String forceString = (reason == FLUSH_REASON_FORCE_FLUSH) ? ". The force flush event "
+ ContentCaptureEvent.getTypeAsString(event.getType()) : "";
- Log.d(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason)
+ Log.v(TAG, "Flushing " + numberEvents + " event(s) for " + getDebugState(reason)
+ forceString);
}
if (mFlushHistory != null) {
diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java
index 8db5a5eb0e47..42bb674d9d85 100644
--- a/core/java/android/window/WindowInfosListener.java
+++ b/core/java/android/window/WindowInfosListener.java
@@ -16,6 +16,8 @@
package android.window;
+import android.Manifest;
+import android.annotation.RequiresPermission;
import android.graphics.Matrix;
import android.util.Pair;
import android.util.Size;
@@ -49,10 +51,13 @@ public abstract class WindowInfosListener {
/**
* Register the WindowInfosListener.
*
+ * Registering WindowInfosListeners should only be done within system_server and shell.
+ *
* @return The cached values for InputWindowHandles and DisplayInfos. This is the last updated
* value that was sent from SurfaceFlinger to this particular process. If there was nothing
* registered previously, then the data can be empty.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
public Pair<InputWindowHandle[], DisplayInfo[]> register() {
return nativeRegister(mNativeListener);
}
@@ -65,8 +70,12 @@ public abstract class WindowInfosListener {
}
private static native long nativeCreate(WindowInfosListener thiz);
+
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
private static native Pair<InputWindowHandle[], DisplayInfo[]> nativeRegister(long ptr);
+
private static native void nativeUnregister(long ptr);
+
private static native long nativeGetFinalizer();
/**
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
new file mode 100644
index 000000000000..429156fb980a
--- /dev/null
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.window;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.InputConfig;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Pair;
+import android.view.InputWindowHandle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+/**
+ * Wrapper class to provide access to WindowInfosListener within tests.
+ *
+ * @hide
+ */
+@TestApi
+public class WindowInfosListenerForTest {
+
+ /**
+ * Window properties passed to {@code @WindowInfosListenerForTest#onWindowInfosChanged}.
+ */
+ public static class WindowInfo {
+ /**
+ * The window's token.
+ */
+ @NonNull
+ public final IBinder windowToken;
+
+ /**
+ * The window's name.
+ */
+ @NonNull
+ public final String name;
+
+ /**
+ * The window's position and size in display space.
+ */
+ @NonNull
+ public final Rect bounds;
+
+ WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) {
+ this.windowToken = windowToken;
+ this.name = name;
+ this.bounds = bounds;
+ }
+ }
+
+ private static final String TAG = "WindowInfosListenerForTest";
+
+ private ArrayMap<Consumer<List<WindowInfo>>, WindowInfosListener> mListeners;
+
+ public WindowInfosListenerForTest() {
+ mListeners = new ArrayMap<>();
+ }
+
+ /**
+ * Register a listener that is called when the system's list of visible windows has changes in
+ * position or visibility.
+ *
+ * @param consumer Consumer that is called with reverse Z ordered lists of WindowInfo instances
+ * where the first value is the topmost window.
+ */
+ @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER)
+ public void addWindowInfosListener(
+ @NonNull Consumer<List<WindowInfo>> consumer) {
+ var calledWithInitialState = new CountDownLatch(1);
+ var listener = new WindowInfosListener() {
+ @Override
+ public void onWindowInfosChanged(InputWindowHandle[] windowHandles,
+ DisplayInfo[] displayInfos) {
+ try {
+ calledWithInitialState.await();
+ } catch (InterruptedException exception) {
+ Log.e(TAG,
+ "Exception thrown while waiting for listener to be called with "
+ + "initial state");
+ }
+ consumer.accept(buildWindowInfos(windowHandles));
+ }
+ };
+ mListeners.put(consumer, listener);
+ Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> initialState =
+ listener.register();
+ consumer.accept(buildWindowInfos(initialState.first));
+ calledWithInitialState.countDown();
+ }
+
+ /**
+ * Unregisters the listener.
+ */
+ public void removeWindowInfosListener(@NonNull Consumer<List<WindowInfo>> consumer) {
+ WindowInfosListener listener = mListeners.remove(consumer);
+ if (listener == null) {
+ return;
+ }
+ listener.unregister();
+ }
+
+ private static List<WindowInfo> buildWindowInfos(InputWindowHandle[] windowHandles) {
+ var windowInfos = new ArrayList<WindowInfo>(windowHandles.length);
+ for (var handle : windowHandles) {
+ if ((handle.inputConfig & InputConfig.NOT_VISIBLE) != 0) {
+ continue;
+ }
+ var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight,
+ handle.frameBottom);
+ windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds));
+ }
+ return windowInfos;
+ }
+}
diff --git a/core/java/com/android/internal/app/procstats/UidState.java b/core/java/com/android/internal/app/procstats/UidState.java
index 8761b7470cd3..49113465c26a 100644
--- a/core/java/com/android/internal/app/procstats/UidState.java
+++ b/core/java/com/android/internal/app/procstats/UidState.java
@@ -150,6 +150,7 @@ public final class UidState {
public void resetSafely(long now) {
mDurations.resetTable();
mStartTime = now;
+ mProcesses.removeIf(p -> !p.isInUse());
}
/**
diff --git a/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
index 6c01780dac76..2a9025d35339 100644
--- a/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractMultiplePendingRequestsRemoteService.java
@@ -61,7 +61,7 @@ public abstract class AbstractMultiplePendingRequestsRemoteService<S
final int size = mPendingRequests.size();
if (mVerbose) Slog.v(mTag, "Sending " + size + " pending requests");
for (int i = 0; i < size; i++) {
- mPendingRequests.get(i).run();
+ handlePendingRequest(mPendingRequests.get(i));
}
mPendingRequests.clear();
}
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index d5f7ba57a694..18414cf93bc8 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -107,7 +107,7 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I
private int mServiceExitSubReason;
/** Requests that have been scheduled, but that are not finished yet */
- private final ArrayList<BasePendingRequest<S, I>> mUnfinishedRequests = new ArrayList<>();
+ protected final ArrayList<BasePendingRequest<S, I>> mUnfinishedRequests = new ArrayList<>();
/**
* Callback called when the service dies.
@@ -673,6 +673,11 @@ public abstract class AbstractRemoteService<S extends AbstractRemoteService<S, I
mCancelled = true;
}
+ S service = mWeakService.get();
+ if (service != null) {
+ service.finishRequest(this);
+ }
+
onCancel();
return true;
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index c41b8223d181..9b9e010039a6 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -295,6 +295,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private boolean mClosingActionMenu;
private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+ private int mAudioMode = AudioManager.MODE_NORMAL;
private MediaController mMediaController;
private AudioManager mAudioManager;
@@ -317,6 +318,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
};
+ private AudioManager.OnModeChangedListener mOnModeChangedListener;
+
private Transition mEnterTransition = null;
private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
private Transition mExitTransition = null;
@@ -1944,7 +1947,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
case KeyEvent.KEYCODE_VOLUME_MUTE: {
// If we have a session and no active phone call send it the volume command,
// otherwise use the suggested stream.
- if (mMediaController != null && !isActivePhoneCallKnown()) {
+ if (mMediaController != null && !isActivePhoneCallOngoing()) {
getMediaSessionManager().dispatchVolumeKeyEventToSessionAsSystemService(event,
mMediaController.getSessionToken());
} else {
@@ -1995,16 +1998,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
return false;
}
- private boolean isActivePhoneCallKnown() {
- boolean isActivePhoneCallKnown = false;
- AudioManager audioManager =
- (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
- int audioManagerMode = audioManager.getMode();
- if (audioManagerMode == AudioManager.MODE_IN_CALL
- || audioManagerMode == AudioManager.MODE_IN_COMMUNICATION) {
- isActivePhoneCallKnown = true;
- }
- return isActivePhoneCallKnown;
+ private boolean isActivePhoneCallOngoing() {
+ return mAudioMode == AudioManager.MODE_IN_CALL
+ || mAudioMode == AudioManager.MODE_IN_COMMUNICATION;
}
private KeyguardManager getKeyguardManager() {
@@ -2331,6 +2327,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
}
+ @Override
+ protected void onDestroy() {
+ if (mOnModeChangedListener != null) {
+ getAudioManager().removeOnModeChangedListener(mOnModeChangedListener);
+ mOnModeChangedListener = null;
+ }
+ }
+
private class PanelMenuPresenterCallback implements MenuPresenter.Callback {
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
@@ -3229,6 +3233,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public void setMediaController(MediaController controller) {
mMediaController = controller;
+ if (controller != null && mOnModeChangedListener == null) {
+ mAudioMode = getAudioManager().getMode();
+ mOnModeChangedListener = mode -> mAudioMode = mode;
+ getAudioManager().addOnModeChangedListener(getContext().getMainExecutor(),
+ mOnModeChangedListener);
+ } else if (mOnModeChangedListener != null) {
+ getAudioManager().removeOnModeChangedListener(mOnModeChangedListener);
+ mOnModeChangedListener = null;
+ }
}
@Override
diff --git a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
index f2b0544bcfad..abe6c7c079c2 100644
--- a/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/BaseProtoLogImpl.java
@@ -72,11 +72,13 @@ public class BaseProtoLogImpl {
private static final String TAG = "ProtoLog";
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
static final String PROTOLOG_VERSION = "1.0.0";
+ private static final int DEFAULT_PER_CHUNK_SIZE = 0;
private final File mLogFile;
private final String mViewerConfigFilename;
private final TraceBuffer mBuffer;
protected final ProtoLogViewerConfigReader mViewerConfig;
+ private final int mPerChunkSize;
private boolean mProtoLogEnabled;
private boolean mProtoLogEnabledLockFree;
@@ -160,7 +162,7 @@ public class BaseProtoLogImpl {
return;
}
try {
- ProtoOutputStream os = new ProtoOutputStream();
+ ProtoOutputStream os = new ProtoOutputStream(mPerChunkSize);
long token = os.start(LOG);
os.write(MESSAGE_HASH, messageHash);
os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos());
@@ -219,10 +221,16 @@ public class BaseProtoLogImpl {
public BaseProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
ProtoLogViewerConfigReader viewerConfig) {
+ this(file, viewerConfigFilename, bufferCapacity, viewerConfig, DEFAULT_PER_CHUNK_SIZE);
+ }
+
+ public BaseProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
+ ProtoLogViewerConfigReader viewerConfig, int perChunkSize) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
+ mPerChunkSize = perChunkSize;
}
/**
@@ -368,7 +376,7 @@ public class BaseProtoLogImpl {
try {
long offset =
(System.currentTimeMillis() - (SystemClock.elapsedRealtimeNanos() / 1000000));
- ProtoOutputStream proto = new ProtoOutputStream();
+ ProtoOutputStream proto = new ProtoOutputStream(mPerChunkSize);
proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
proto.write(VERSION, PROTOLOG_VERSION);
proto.write(REAL_TIME_TO_ELAPSED_TIME_OFFSET_MILLIS, offset);
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 353c6c083d9d..527cfddf6d8e 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -30,6 +30,7 @@ public class ProtoLogImpl extends BaseProtoLogImpl {
private static final int BUFFER_CAPACITY = 1024 * 1024;
private static final String LOG_FILENAME = "/data/misc/wmtrace/wm_log.winscope";
private static final String VIEWER_CONFIG_FILENAME = "/system/etc/protolog.conf.json.gz";
+ private static final int PER_CHUNK_SIZE = 1024;
private static ProtoLogImpl sServiceInstance = null;
@@ -94,7 +95,10 @@ public class ProtoLogImpl extends BaseProtoLogImpl {
public static synchronized ProtoLogImpl getSingleInstance() {
if (sServiceInstance == null) {
sServiceInstance = new ProtoLogImpl(
- new File(LOG_FILENAME), BUFFER_CAPACITY, new ProtoLogViewerConfigReader());
+ new File(LOG_FILENAME)
+ , BUFFER_CAPACITY
+ , new ProtoLogViewerConfigReader()
+ , PER_CHUNK_SIZE);
}
return sServiceInstance;
}
@@ -105,8 +109,8 @@ public class ProtoLogImpl extends BaseProtoLogImpl {
}
public ProtoLogImpl(File logFile, int bufferCapacity,
- ProtoLogViewerConfigReader viewConfigReader) {
- super(logFile, VIEWER_CONFIG_FILENAME, bufferCapacity, viewConfigReader);
- }
+ ProtoLogViewerConfigReader viewConfigReader, int perChunkSize) {
+ super(logFile, VIEWER_CONFIG_FILENAME, bufferCapacity, viewConfigReader, perChunkSize);
+ }
}
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index b09a9c3a505d..739055e040af 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -50,12 +50,22 @@ static struct {
struct {
jclass clazz;
+
jmethodID init;
+
+ jfieldID vsyncId;
+ jfieldID expectedPresentationTime;
+ jfieldID deadline;
} frameTimelineClassInfo;
struct {
jclass clazz;
+
jmethodID init;
+
+ jfieldID frameInterval;
+ jfieldID preferredFrameTimelineIndex;
+ jfieldID frameTimelines;
} vsyncEventDataClassInfo;
} gDisplayEventReceiverClassInfo;
@@ -63,7 +73,7 @@ static struct {
class NativeDisplayEventReceiver : public DisplayEventDispatcher {
public:
- NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
+ NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak,
const sp<MessageQueue>& messageQueue, jint vsyncSource,
jint eventRegistration, jlong layerHandle);
@@ -74,6 +84,7 @@ protected:
private:
jobject mReceiverWeakGlobal;
+ jobject mVsyncEventDataWeakGlobal;
sp<MessageQueue> mMessageQueue;
void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
@@ -87,6 +98,7 @@ private:
};
NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
+ jobject vsyncEventDataWeak,
const sp<MessageQueue>& messageQueue,
jint vsyncSource, jint eventRegistration,
jlong layerHandle)
@@ -98,6 +110,7 @@ NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject rece
reinterpret_cast<IBinder*>(layerHandle))
: nullptr),
mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
+ mVsyncEventDataWeakGlobal(env->NewGlobalRef(vsyncEventDataWeak)),
mMessageQueue(messageQueue) {
ALOGV("receiver %p ~ Initializing display event receiver.", this);
}
@@ -144,12 +157,39 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla
JNIEnv* env = AndroidRuntime::getJNIEnv();
ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
- if (receiverObj.get()) {
+ ScopedLocalRef<jobject> vsyncEventDataObj(env, GetReferent(env, mVsyncEventDataWeakGlobal));
+ if (receiverObj.get() && vsyncEventDataObj.get()) {
ALOGV("receiver %p ~ Invoking vsync handler.", this);
- jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData);
+ env->SetIntField(vsyncEventDataObj.get(),
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo
+ .preferredFrameTimelineIndex,
+ vsyncEventData.preferredFrameTimelineIndex);
+ env->SetLongField(vsyncEventDataObj.get(),
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval,
+ vsyncEventData.frameInterval);
+
+ jobjectArray frameTimelinesObj = reinterpret_cast<jobjectArray>(
+ env->GetObjectField(vsyncEventDataObj.get(),
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo
+ .frameTimelines));
+ for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+ VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i];
+ jobject frameTimelineObj = env->GetObjectArrayElement(frameTimelinesObj, i);
+ env->SetLongField(frameTimelineObj,
+ gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId,
+ frameTimeline.vsyncId);
+ env->SetLongField(frameTimelineObj,
+ gDisplayEventReceiverClassInfo.frameTimelineClassInfo
+ .expectedPresentationTime,
+ frameTimeline.expectedPresentationTime);
+ env->SetLongField(frameTimelineObj,
+ gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline,
+ frameTimeline.deadlineTimestamp);
+ }
+
env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
- timestamp, displayId.value, count, javaVsyncEventData);
+ timestamp, displayId.value, count);
ALOGV("receiver %p ~ Returned from vsync handler.", this);
}
@@ -217,8 +257,9 @@ void NativeDisplayEventReceiver::dispatchFrameRateOverrides(
mMessageQueue->raiseAndClearException(env, "dispatchModeChanged");
}
-static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj,
- jint vsyncSource, jint eventRegistration, jlong layerHandle) {
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak,
+ jobject messageQueueObj, jint vsyncSource, jint eventRegistration,
+ jlong layerHandle) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
if (messageQueue == NULL) {
jniThrowRuntimeException(env, "MessageQueue is not initialized.");
@@ -226,8 +267,8 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject
}
sp<NativeDisplayEventReceiver> receiver =
- new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource,
- eventRegistration, layerHandle);
+ new NativeDisplayEventReceiver(env, receiverWeak, vsyncEventDataWeak, messageQueue,
+ vsyncSource, eventRegistration, layerHandle);
status_t status = receiver->initialize();
if (status) {
String8 message;
@@ -274,7 +315,9 @@ static jobject nativeGetLatestVsyncEventData(JNIEnv* env, jclass clazz, jlong re
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J",
+ {"nativeInit",
+ "(Ljava/lang/ref/WeakReference;Ljava/lang/ref/WeakReference;Landroid/os/"
+ "MessageQueue;IIJ)J",
(void*)nativeInit},
{"nativeGetDisplayEventReceiverFinalizer", "()J",
(void*)nativeGetDisplayEventReceiverFinalizer},
@@ -291,8 +334,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz);
gDisplayEventReceiverClassInfo.dispatchVsync =
- GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync",
- "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V");
+ GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V");
gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env,
gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
gDisplayEventReceiverClassInfo.dispatchModeChanged =
@@ -318,6 +360,15 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init =
GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
"<init>", "(JJJ)V");
+ gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId =
+ GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
+ "vsyncId", "J");
+ gDisplayEventReceiverClassInfo.frameTimelineClassInfo.expectedPresentationTime =
+ GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
+ "expectedPresentationTime", "J");
+ gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline =
+ GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz,
+ "deadline", "J");
jclass vsyncEventDataClazz =
FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData");
@@ -329,6 +380,17 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) {
"([Landroid/view/"
"DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V");
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex =
+ GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+ "preferredFrameTimelineIndex", "I");
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval =
+ GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+ "frameInterval", "J");
+ gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameTimelines =
+ GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
+ "frameTimelines",
+ "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;");
+
return res;
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 4bc567abf27a..ad54004ec81c 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -20,20 +20,21 @@
#include <android_runtime/AndroidRuntime.h>
#include <input/InputTransport.h>
+#include <inttypes.h>
#include <log/log.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Looper.h>
+
+#include <optional>
+#include <unordered_map>
+
#include "android_os_MessageQueue.h"
#include "android_view_InputChannel.h"
#include "android_view_KeyEvent.h"
#include "android_view_MotionEvent.h"
#include "core_jni_helpers.h"
-#include <inttypes.h>
-#include <unordered_map>
-
-
using android::base::Result;
namespace android {
@@ -67,7 +68,7 @@ private:
jobject mSenderWeakGlobal;
InputPublisher mInputPublisher;
sp<MessageQueue> mMessageQueue;
- std::unordered_map<uint32_t, uint32_t> mPublishedSeqMap;
+ std::unordered_map<uint32_t, std::optional<uint32_t>> mPublishedSeqMap;
uint32_t mNextPublishedSeq;
@@ -165,8 +166,14 @@ status_t NativeInputEventSender::sendMotionEvent(uint32_t seq, const MotionEvent
getInputChannelName().c_str(), status);
return status;
}
+ // mPublishedSeqMap tracks all sequences published from this sender. Only the last
+ // sequence number is used to signal this motion event is finished.
+ if (i == event->getHistorySize()) {
+ mPublishedSeqMap.emplace(publishedSeq, seq);
+ } else {
+ mPublishedSeqMap.emplace(publishedSeq, std::nullopt);
+ }
}
- mPublishedSeqMap.emplace(publishedSeq, seq);
return OK;
}
@@ -277,8 +284,16 @@ bool NativeInputEventSender::notifyConsumerResponse(
// does something wrong and sends bad data. Just ignore and process other events.
return true;
}
- const uint32_t seq = it->second;
+
+ const std::optional<uint32_t> seqOptional = it->second;
mPublishedSeqMap.erase(it);
+ // If this optional does not have a value, it means we are processing an event that had history
+ // and was split. There are more events coming, so we can't call 'dispatchInputEventFinished'
+ // yet. The final split event will have a valid sequence number.
+ if (!seqOptional.has_value()) {
+ return true;
+ }
+ const uint32_t seq = seqOptional.value();
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Received finished signal, seq=%u, handled=%s, pendingEvents=%zu.",
diff --git a/core/jni/android_view_MotionPredictor.cpp b/core/jni/android_view_MotionPredictor.cpp
index 2c232fadbbc5..de3e81c7088b 100644
--- a/core/jni/android_view_MotionPredictor.cpp
+++ b/core/jni/android_view_MotionPredictor.cpp
@@ -20,7 +20,6 @@
#include <input/MotionPredictor.h>
#include "android_view_MotionEvent.h"
-#include "core_jni_converters.h"
#include "core_jni_helpers.h"
/**
@@ -30,14 +29,6 @@
namespace android {
-// ----------------------------------------------------------------------------
-
-static struct {
- jclass clazz;
-} gMotionEventClassInfo;
-
-// ----------------------------------------------------------------------------
-
static void release(void* ptr) {
delete reinterpret_cast<MotionPredictor*>(ptr);
}
@@ -57,14 +48,20 @@ static void android_view_MotionPredictor_nativeRecord(JNIEnv* env, jclass clazz,
jobject event) {
MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, event);
- predictor->record(*motionEvent);
+
+ android::base::Result<void> result = predictor->record(*motionEvent);
+ if (!result.ok()) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ result.error().message().c_str());
+ }
}
static jobject android_view_MotionPredictor_nativePredict(JNIEnv* env, jclass clazz, jlong ptr,
jlong predictionTimeNanos) {
MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
- return toJavaArray(env, predictor->predict(static_cast<nsecs_t>(predictionTimeNanos)),
- gMotionEventClassInfo.clazz, &android_view_MotionEvent_obtainFromNative);
+ return android_view_MotionEvent_obtainFromNative(env,
+ predictor->predict(static_cast<nsecs_t>(
+ predictionTimeNanos)));
}
static jboolean android_view_MotionPredictor_nativeIsPredictionAvailable(JNIEnv* env, jclass clazz,
@@ -84,15 +81,13 @@ static const std::array<JNINativeMethod, 5> gMotionPredictorMethods{{
(void*)android_view_MotionPredictor_nativeGetNativeMotionPredictorFinalizer},
{"nativeRecord", "(JLandroid/view/MotionEvent;)V",
(void*)android_view_MotionPredictor_nativeRecord},
- {"nativePredict", "(JJ)[Landroid/view/MotionEvent;",
+ {"nativePredict", "(JJ)Landroid/view/MotionEvent;",
(void*)android_view_MotionPredictor_nativePredict},
{"nativeIsPredictionAvailable", "(JII)Z",
(void*)android_view_MotionPredictor_nativeIsPredictionAvailable},
}};
int register_android_view_MotionPredictor(JNIEnv* env) {
- jclass motionEventClazz = FindClassOrDie(env, "android/view/MotionEvent");
- gMotionEventClassInfo.clazz = MakeGlobalRefOrDie(env, motionEventClazz);
return RegisterMethodsOrDie(env, "android/view/MotionPredictor", gMotionPredictorMethods.data(),
gMotionPredictorMethods.size());
}
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 146ac9d2c425..7503dde440e0 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -115,6 +115,9 @@ message SystemSettingsProto {
optional SettingProto sound_cache = 2;
optional SettingProto light_pulse = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto vibration_intensity = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto camera_flash_notification = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto screen_flash_notification = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto screen_flash_notification_color_global = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Notification notification = 17;
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index ab7b0ab8d1fc..ed612a05e6b5 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -142,6 +142,7 @@ message BroadcastQueueProto {
optional int64 finish_clock_time_ms = 4;
}
repeated BroadcastSummary historical_broadcasts_summary = 6;
+ repeated BroadcastRecordProto pending_broadcasts = 7;
}
message MemInfoDumpProto {
@@ -439,6 +440,7 @@ message ServiceRecordProto {
optional int32 id = 1;
optional .android.app.NotificationProto notification = 2;
+ optional int32 foregroundServiceType = 3;
}
optional Foreground foreground = 13;
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 3c4bac8156d2..b71995f899c5 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -178,3 +178,12 @@ genrule {
" > $(out)",
tools: ["xmllint"],
}
+
+filegroup {
+ name: "frameworks-base-core-AndroidManifest.xml",
+ srcs: ["AndroidManifest.xml"],
+ visibility: [
+ "//frameworks/base",
+ "//frameworks/base/api",
+ ],
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b73623031dc1..cb6c09241807 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -822,6 +822,8 @@
<protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" />
<protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" />
<protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" />
+ <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" />
+ <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -4482,8 +4484,8 @@
<!-- Allows an application to be able to store and retrieve credentials from a remote
device.
@hide @SystemApi -->
- <permission android:name="android.permission.PROVIDE_HYBRID_CREDENTIAL_SERVICE"
- android:protectionLevel="signature|privileged" />
+ <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
+ android:protectionLevel="signature|privileged|role" />
<!-- ========================================= -->
<!-- Permissions for special development tools -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 47d771fea12d..07f353025f87 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4746,8 +4746,7 @@
<string name="accessibility_shortcut_disabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned off.</string>
<!-- Text spoken when accessibility shortcut warning dialog is shown. [CHAR LIMIT=none] -->
- <string name="accessibility_shortcut_spoken_feedback">Press and hold both volume keys for three seconds to use
- <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g></string>
+ <string name="accessibility_shortcut_spoken_feedback">Release the volume keys. To turn on <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g>, press and hold both volume keys again for 3 seconds.</string>
<!-- Text appearing in a prompt at the top of UI allowing the user to select a target service or feature to be assigned to the Accessibility button in the navigation bar. [CHAR LIMIT=none]-->
<string name="accessibility_button_prompt_text">Choose a feature to use when you tap the accessibility button:</string>
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
deleted file mode 100644
index 16ed3ef42da3..000000000000
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
+++ /dev/null
@@ -1,104 +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 android.companion.virtual.sensor;
-
-import static android.hardware.Sensor.TYPE_ACCELEROMETER;
-import static android.hardware.SensorDirectChannel.RATE_STOP;
-import static android.hardware.SensorDirectChannel.RATE_VERY_FAST;
-import static android.hardware.SensorDirectChannel.TYPE_HARDWARE_BUFFER;
-import static android.hardware.SensorDirectChannel.TYPE_MEMORY_FILE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.os.Parcel;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualSensorConfigTest {
-
- private static final String SENSOR_NAME = "VirtualSensorName";
- private static final String SENSOR_VENDOR = "VirtualSensorVendor";
-
- @Test
- public void parcelAndUnparcel_matches() {
- final VirtualSensorConfig originalConfig =
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setVendor(SENSOR_VENDOR)
- .setHighestDirectReportRateLevel(RATE_VERY_FAST)
- .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
- .build();
- final Parcel parcel = Parcel.obtain();
- originalConfig.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(0);
- final VirtualSensorConfig recreatedConfig =
- VirtualSensorConfig.CREATOR.createFromParcel(parcel);
- assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
- assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
- assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
- assertThat(recreatedConfig.getHighestDirectReportRateLevel()).isEqualTo(RATE_VERY_FAST);
- assertThat(recreatedConfig.getDirectChannelTypesSupported()).isEqualTo(TYPE_MEMORY_FILE);
- // From hardware/libhardware/include/hardware/sensors-base.h:
- // 0x400 is SENSOR_FLAG_DIRECT_CHANNEL_ASHMEM (i.e. TYPE_MEMORY_FILE)
- // 0x800 is SENSOR_FLAG_DIRECT_CHANNEL_GRALLOC (i.e. TYPE_HARDWARE_BUFFER)
- // 7 is SENSOR_FLAG_SHIFT_DIRECT_REPORT
- assertThat(recreatedConfig.getFlags()).isEqualTo(0x400 | RATE_VERY_FAST << 7);
- }
-
- @Test
- public void hardwareBufferDirectChannelTypeSupported_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setDirectChannelTypesSupported(TYPE_HARDWARE_BUFFER | TYPE_MEMORY_FILE));
- }
-
- @Test
- public void directChannelTypeSupported_missingHighestReportRateLevel_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setDirectChannelTypesSupported(TYPE_MEMORY_FILE)
- .build());
- }
-
- @Test
- public void directChannelTypeSupported_missingDirectChannelTypeSupported_throwsException() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
- .setHighestDirectReportRateLevel(RATE_VERY_FAST)
- .build());
- }
-
- @Test
- public void sensorConfig_onlyRequiredFields() {
- final VirtualSensorConfig config =
- new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
- assertThat(config.getVendor()).isNull();
- assertThat(config.getHighestDirectReportRateLevel()).isEqualTo(RATE_STOP);
- assertThat(config.getDirectChannelTypesSupported()).isEqualTo(0);
- assertThat(config.getFlags()).isEqualTo(0);
- }
-}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
deleted file mode 100644
index c260ef90cd4e..000000000000
--- a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
+++ /dev/null
@@ -1,82 +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 android.companion.virtual.sensor;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-
-import android.os.Parcel;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualSensorEventTest {
-
- private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
- private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
-
- @Test
- public void parcelAndUnparcel_matches() {
- final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
- .setTimestampNanos(TIMESTAMP_NANOS)
- .build();
- final Parcel parcel = Parcel.obtain();
- originalEvent.writeToParcel(parcel, /* flags= */ 0);
- parcel.setDataPosition(0);
- final VirtualSensorEvent recreatedEvent =
- VirtualSensorEvent.CREATOR.createFromParcel(parcel);
- assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
- assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
- }
-
- @Test
- public void sensorEvent_nullValues() {
- assertThrows(
- IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
- }
-
- @Test
- public void sensorEvent_noValues() {
- assertThrows(
- IllegalArgumentException.class,
- () -> new VirtualSensorEvent.Builder(new float[0]).build());
- }
-
- @Test
- public void sensorEvent_noTimestamp_usesCurrentTime() {
- final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
- assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
- assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
- assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
- }
-
- @Test
- public void sensorEvent_created() {
- final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
- .setTimestampNanos(TIMESTAMP_NANOS)
- .build();
- assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
- assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
- }
-}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index e48f8a01d738..a0d8dcf830e8 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -28,6 +28,7 @@ import kotlin.math.ceil
import kotlin.math.floor
import org.junit.Test
import org.junit.runner.RunWith
+import kotlin.random.Random.Default.nextFloat
@Presubmit
@RunWith(AndroidJUnit4::class)
@@ -124,10 +125,12 @@ class FontScaleConverterFactoryTest {
@LargeTest
@Test
fun allFeasibleScalesAndConversionsDoNotCrash() {
- generateSequenceOfFractions(-10f..10f, step = 0.01f)
- .mapNotNull{ FontScaleConverterFactory.forScale(it) }!!
+ generateSequenceOfFractions(-10f..10f, step = 0.1f)
+ .fuzzFractions()
+ .mapNotNull{ FontScaleConverterFactory.forScale(it) }
.flatMap{ table ->
- generateSequenceOfFractions(-2000f..2000f, step = 0.01f)
+ generateSequenceOfFractions(-2000f..2000f, step = 0.1f)
+ .fuzzFractions()
.map{ Pair(table, it) }
}
.forEach { (table, sp) ->
@@ -172,6 +175,30 @@ class FontScaleConverterFactoryTest {
assertThat(fractions).doesNotContain(-.35f)
}
+ @Test
+ fun testFuzzFractions() {
+ val numFuzzedFractions = 6
+ val fractions = generateSequenceOfFractions(-1000f..1000f, step = 0.1f)
+ .fuzzFractions()
+ .toList()
+ fractions.forEach {
+ assertThat(it).isAtLeast(-1000f)
+ assertThat(it).isLessThan(1001f)
+ }
+
+ val numGeneratedFractions = 1000 * 2 * 10 + 1 // Don't forget the 0 in the middle!
+ assertThat(fractions).hasSize(numGeneratedFractions * numFuzzedFractions)
+
+ assertThat(fractions).contains(100f)
+ assertThat(fractions).contains(500.1f)
+ assertThat(fractions).contains(500.2f)
+ assertThat(fractions).contains(0.2f)
+ assertThat(fractions).contains(0f)
+ assertThat(fractions).contains(-10f)
+ assertThat(fractions).contains(-10f)
+ assertThat(fractions).contains(-10.3f)
+ }
+
companion object {
private const val CONVERSION_TOLERANCE = 0.05f
}
@@ -188,3 +215,9 @@ fun generateSequenceOfFractions(
.takeWhile { it <= endInclusive }
.map{ it.toFloat() / multiplier }
}
+
+private fun Sequence<Float>.fuzzFractions(): Sequence<Float> {
+ return flatMap { i ->
+ listOf(i, i + 0.01f, i + 0.054f, i + 0.099f, i + nextFloat(), i + nextFloat())
+ }
+}
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index 43334ab08b2f..e31d5aef9b69 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.slice.Slice;
import android.content.Context;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -47,6 +48,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -59,6 +61,17 @@ public class CredentialManagerTest {
@Mock
private Activity mMockActivity;
+ private static final int TEST_USER_ID = 1;
+ private static final CredentialProviderInfo TEST_CREDENTIAL_PROVIDER_INFO =
+ new CredentialProviderInfo.Builder(new ServiceInfo())
+ .setSystemProvider(true)
+ .setOverrideLabel("test")
+ .addCapabilities(Arrays.asList("passkey"))
+ .setEnabled(true)
+ .build();
+ private static final List<CredentialProviderInfo> TEST_CREDENTIAL_PROVIDER_INFO_LIST =
+ Arrays.asList(TEST_CREDENTIAL_PROVIDER_INFO);
+
private GetCredentialRequest mGetRequest;
private CreateCredentialRequest mCreateRequest;
@@ -438,95 +451,53 @@ public class CredentialManagerTest {
}
@Test
- public void testListEnabledProviders_nullExecutor() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.listEnabledProviders(null, null, result -> {
- }));
-
+ public void testGetCredentialProviderServices_allProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
}
@Test
- public void testListEnabledProviders_nullCallback() {
- assertThrows(NullPointerException.class,
- () -> mCredentialManager.listEnabledProviders(null, mExecutor, null));
-
+ public void testGetCredentialProviderServices_userProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_alreadyCancelled() throws RemoteException {
- final CancellationSignal cancellation = new CancellationSignal();
- cancellation.cancel();
-
- mCredentialManager.listEnabledProviders(cancellation, mExecutor, result -> {
- });
-
- verify(mMockCredentialManagerService, never()).listEnabledProviders(any());
+ public void testGetCredentialProviderServices_systemProviders() throws RemoteException {
+ verifyGetCredentialProviderServices(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_cancel() throws RemoteException {
- final ICancellationSignal serviceSignal = mock(ICancellationSignal.class);
- final CancellationSignal cancellation = new CancellationSignal();
-
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
-
- when(mMockCredentialManagerService.listEnabledProviders(any())).thenReturn(serviceSignal);
-
- mCredentialManager.listEnabledProviders(cancellation, mExecutor, callback);
-
- verify(mMockCredentialManagerService).listEnabledProviders(any());
-
- cancellation.cancel();
- verify(serviceSignal).cancel();
+ public void testGetCredentialProviderServicesForTesting_allProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
}
@Test
- public void testListEnabledProviders_failed() throws RemoteException {
- ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- IListEnabledProvidersCallback.class);
- ArgumentCaptor<ListEnabledProvidersException> errorCaptor = ArgumentCaptor.forClass(
- ListEnabledProvidersException.class);
-
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
-
- when(mMockCredentialManagerService.listEnabledProviders(
- callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.listEnabledProviders(null, mExecutor, callback);
- verify(mMockCredentialManagerService).listEnabledProviders(any());
-
- final String errorType = "type";
- callbackCaptor.getValue().onError("type", "unknown error");
- verify(callback).onError(errorCaptor.capture());
-
- assertThat(errorCaptor.getValue().getType()).isEqualTo(errorType);
+ public void testGetCredentialProviderServicesForTesting_userProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY);
}
@Test
- public void testListEnabledProviders_success() throws RemoteException {
- ListEnabledProvidersResponse response = ListEnabledProvidersResponse.create(
- List.of("foo", "bar", "baz"));
+ public void testGetCredentialProviderServicesForTesting_systemProviders() throws RemoteException {
+ verifyGetCredentialProviderServicesForTesting(CredentialManager.PROVIDER_FILTER_SYSTEM_PROVIDERS_ONLY);
+ }
- OutcomeReceiver<ListEnabledProvidersResponse, ListEnabledProvidersException> callback =
- mock(OutcomeReceiver.class);
+ private void verifyGetCredentialProviderServices(int testFilter) throws RemoteException {
+ when(mMockCredentialManagerService.getCredentialProviderServices(
+ TEST_USER_ID, testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
- ArgumentCaptor<IListEnabledProvidersCallback> callbackCaptor = ArgumentCaptor.forClass(
- IListEnabledProvidersCallback.class);
- ArgumentCaptor<ListEnabledProvidersResponse> responseCaptor = ArgumentCaptor.forClass(
- ListEnabledProvidersResponse.class);
+ List<CredentialProviderInfo> output =
+ mCredentialManager.getCredentialProviderServices(TEST_USER_ID, testFilter);
- when(mMockCredentialManagerService.listEnabledProviders(
- callbackCaptor.capture())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.listEnabledProviders(null, mExecutor, callback);
+ assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
+ }
- verify(mMockCredentialManagerService).listEnabledProviders(any());
+ private void verifyGetCredentialProviderServicesForTesting(int testFilter) throws RemoteException {
+ when(mMockCredentialManagerService.getCredentialProviderServicesForTesting(
+ testFilter)).thenReturn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
- callbackCaptor.getValue().onResponse(response);
+ List<CredentialProviderInfo> output =
+ mCredentialManager.getCredentialProviderServicesForTesting(testFilter);
- verify(callback).onResult(responseCaptor.capture());
- assertThat(responseCaptor.getValue().getProviderComponentNames()).containsExactlyElementsIn(
- response.getProviderComponentNames());
+ assertThat(output).containsExactlyElementsIn(TEST_CREDENTIAL_PROVIDER_INFO_LIST);
}
@Test
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
index 9b9a84b79da3..35b3267ea301 100644
--- a/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/procstats/ProcessStatsTest.java
@@ -156,4 +156,19 @@ public class ProcessStatsTest extends TestCase {
eq(0),
eq(APP_1_PROCESS_NAME));
}
+
+ @SmallTest
+ public void testSafelyResetClearsProcessInUidState() 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.makeActive();
+ UidState uidState = processStats.mUidStates.get(APP_1_UID);
+ assertTrue(uidState.isInUse());
+ processState.makeInactive();
+ uidState.resetSafely(NOW_MS);
+ processState.makeActive();
+ assertFalse(uidState.isInUse());
+ }
}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 05e17720b175..913eaf2391f6 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1297,6 +1297,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-894942237": {
+ "message": "Force Playing Transition: %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-883738232": {
"message": "Adding more than one toast window for UID at a time.",
"level": "WARN",
@@ -4243,12 +4249,6 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
- "1878927091": {
- "message": "prepareSurface: No changes in animation for %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ANIM",
- "at": "com\/android\/server\/wm\/WindowStateAnimator.java"
- },
"1891501279": {
"message": "cancelAnimation(): reason=%s",
"level": "DEBUG",
diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java
index feedb7d3e2bf..9ac84a6159da 100644
--- a/graphics/java/android/graphics/Gainmap.java
+++ b/graphics/java/android/graphics/Gainmap.java
@@ -25,13 +25,11 @@ import libcore.util.NativeAllocationRegistry;
/**
* Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable
- * display adjustment capability.
- *
- * It is a combination of a set of metadata describing how to apply the gainmap, as well as either
- * a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
+ * display adjustment capability. It is a combination of a set of metadata describing how to apply
+ * the gainmap, as well as either a 1 (such as {@link android.graphics.Bitmap.Config#ALPHA_8} or 3
* (such as {@link android.graphics.Bitmap.Config#ARGB_8888} with the alpha channel ignored)
* channel Bitmap that represents the gainmap data itself.
- *
+ * <p>
* When rendering to an {@link android.content.pm.ActivityInfo#COLOR_MODE_HDR} activity, the
* hardware accelerated {@link Canvas} will automatically apply the gainmap when sufficient
* HDR headroom is available.
@@ -45,7 +43,7 @@ import libcore.util.NativeAllocationRegistry;
* image, often at a lower resolution (such as 1/4th), along with some metadata to describe
* how to apply the gainmap. The gainmap image itself is then a greyscale image representing
* the transformation to apply onto the base image to reconstruct an HDR rendition of it.
- *
+ * <p>
* As such these "gainmap images" consist of 3 parts - a base {@link Bitmap} with a
* {@link Bitmap#getGainmap()} that returns an instance of this class which in turn contains
* the enhancement layer represented as another Bitmap, accessible via {@link #getGainmapContents()}
@@ -55,25 +53,27 @@ import libcore.util.NativeAllocationRegistry;
* When doing custom rendering such as to an OpenGL ES or Vulkan context, the gainmap is not
* automatically applied. In such situations, the following steps are appropriate to render the
* gainmap in combination with the base image.
- *
+ * <p>
* Suppose our display has HDR to SDR ratio of H, and we wish to display an image with gainmap on
* this display. Let B be the pixel value from the base image in a color space that has the
* primaries of the base image and a linear transfer function. Let G be the pixel value from the
* gainmap. Let D be the output pixel in the same color space as B. The value of D is computed
* as follows:
- *
+ * <p>
* First, let W be a weight parameter determining how much the gainmap will be applied.
+ * <pre class="prettyprint">
* W = clamp((log(H) - log(minDisplayRatioForHdrTransition)) /
- * (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)
+ * (log(displayRatioForFullHdr) - log(minDisplayRatioForHdrTransition), 0, 1)</pre>
*
* Next, let L be the gainmap value in log space. We compute this from the value G that was
* sampled from the texture as follows:
- * L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))
- *
+ * <pre class="prettyprint">
+ * L = mix(log(ratioMin), log(ratioMax), pow(G, gamma))</pre>
* Finally, apply the gainmap to compute D, the displayed pixel. If the base image is SDR then
* compute:
- * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr
- *
+ * <pre class="prettyprint">
+ * D = (B + epsilonSdr) * exp(L * W) - epsilonHdr</pre>
+ * <p>
* In the above math, log() is a natural logarithm and exp() is natural exponentiation. The base
* for these functions cancels out and does not affect the result, so other bases may be used
* if preferred.
@@ -150,7 +150,6 @@ public final class Gainmap implements Parcelable {
/**
* Sets the gainmap ratio min. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setRatioMin(float r, float g, float b) {
nSetRatioMin(mNativePtr, r, g, b);
}
@@ -169,7 +168,6 @@ public final class Gainmap implements Parcelable {
/**
* Sets the gainmap ratio max. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setRatioMax(float r, float g, float b) {
nSetRatioMax(mNativePtr, r, g, b);
}
@@ -188,7 +186,6 @@ public final class Gainmap implements Parcelable {
/**
* Sets the gainmap gamma. For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setGamma(float r, float g, float b) {
nSetGamma(mNativePtr, r, g, b);
}
@@ -208,7 +205,6 @@ public final class Gainmap implements Parcelable {
* Sets the sdr epsilon which is used to avoid numerical instability.
* For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setEpsilonSdr(float r, float g, float b) {
nSetEpsilonSdr(mNativePtr, r, g, b);
}
@@ -228,7 +224,6 @@ public final class Gainmap implements Parcelable {
* Sets the hdr epsilon which is used to avoid numerical instability.
* For single-plane gainmaps, r, g, and b should be the same.
*/
- @NonNull
public void setEpsilonHdr(float r, float g, float b) {
nSetEpsilonHdr(mNativePtr, r, g, b);
}
@@ -248,8 +243,7 @@ public final class Gainmap implements Parcelable {
* Sets the hdr/sdr ratio at which point the gainmap is fully applied.
* @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f
*/
- @NonNull
- public void setDisplayRatioForFullHdr(float max) {
+ public void setDisplayRatioForFullHdr(@FloatRange(from = 1.0f) float max) {
if (!Float.isFinite(max) || max < 1f) {
throw new IllegalArgumentException(
"setDisplayRatioForFullHdr must be >= 1.0f, got = " + max);
@@ -269,7 +263,6 @@ public final class Gainmap implements Parcelable {
* Sets the hdr/sdr ratio below which only the SDR image is displayed.
* @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f
*/
- @NonNull
public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) {
if (!Float.isFinite(min) || min < 1f) {
throw new IllegalArgumentException(
diff --git a/graphics/java/android/graphics/text/GraphemeBreak.java b/graphics/java/android/graphics/text/GraphemeBreak.java
new file mode 100644
index 000000000000..f82b2fd659cc
--- /dev/null
+++ b/graphics/java/android/graphics/text/GraphemeBreak.java
@@ -0,0 +1,59 @@
+/*
+ * 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 android.graphics.text;
+
+/** @hide */
+public class GraphemeBreak {
+ private GraphemeBreak() { }
+
+ /**
+ * Util method that checks if the offsets in given range are grapheme break.
+ *
+ * @param advances the advances of characters in the given text. It contains the font
+ * information used by the algorithm to determine the grapheme break. It's useful
+ * when some character is missing in the font. For example, if the smile emoji
+ * "0xD83D 0xDE0A" is not found in the font and is displayed as 2 characters.
+ * We can't treat it as a single grapheme cluster.
+ * @param text the text to be processed.
+ * @param start the start offset of the queried range, inclusive.
+ * @param end the end offset of the queried range, exclusive.
+ * @param isGraphemeBreak the array to receive the result. The i-th element of the
+ * array will be set to true if the offset (start + i) is a grapheme
+ * break; otherwise, it will be set to false.
+ */
+ public static void isGraphemeBreak(float[] advances, char[] text, int start, int end,
+ boolean[] isGraphemeBreak) {
+ if (start > end) {
+ throw new IllegalArgumentException("start is greater than end: start = " + start
+ + " end = " + end);
+ }
+ if (advances.length < end) {
+ throw new IllegalArgumentException("the length of advances is less than end"
+ + "advances.length = " + advances.length
+ + " end = " + end);
+ }
+ if (isGraphemeBreak.length < end - start) {
+ throw new IndexOutOfBoundsException("isGraphemeBreak doesn't have enough space to "
+ + "receive the result, isGraphemeBreak.length: " + isGraphemeBreak.length
+ + " needed space: " + (end - start));
+ }
+ nIsGraphemeBreak(advances, text, start, end, isGraphemeBreak);
+ }
+
+ private static native void nIsGraphemeBreak(float[] advances, char[] text, int start, int end,
+ boolean[] isGraphemeBreak);
+}
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 36c0cb6dfe19..852fae695046 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
@@ -61,6 +61,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.service.notification.NotificationListenerService;
@@ -129,6 +130,15 @@ public class BubbleController implements ConfigurationChangeListener {
private static final String SYSTEM_DIALOG_REASON_KEY = "reason";
private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
+ // TODO(b/256873975) Should use proper flag when available to shell/launcher
+ /**
+ * Whether bubbles are showing in the bubble bar from launcher. This is only available
+ * on large screens and {@link BubbleController#isShowingAsBubbleBar()} should be used
+ * to check all conditions that indicate if the bubble bar is in use.
+ */
+ private static final boolean BUBBLE_BAR_ENABLED =
+ SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
+
private final Context mContext;
private final BubblesImpl mImpl = new BubblesImpl();
private Bubbles.BubbleExpandListener mExpandListener;
@@ -155,9 +165,6 @@ public class BubbleController implements ConfigurationChangeListener {
private final ShellExecutor mBackgroundExecutor;
- // Whether or not we should show bubbles pinned at the bottom of the screen.
- private boolean mIsBubbleBarEnabled;
-
private BubbleLogger mLogger;
private BubbleData mBubbleData;
@Nullable private BubbleStackView mStackView;
@@ -540,10 +547,10 @@ public class BubbleController implements ConfigurationChangeListener {
mDataRepository.removeBubblesForUser(removedUserId, parentUserId);
}
- // TODO(b/256873975): Should pass this into the constructor once flags are available to shell.
- /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */
- public void setBubbleBarEnabled(boolean enabled) {
- mIsBubbleBarEnabled = enabled;
+ /** Whether bubbles are showing in the bubble bar. */
+ public boolean isShowingAsBubbleBar() {
+ // TODO(b/269670598): should also check that we're in gesture nav
+ return BUBBLE_BAR_ENABLED && mBubblePositioner.isLargeScreen();
}
/** Whether this userId belongs to the current user. */
@@ -612,12 +619,6 @@ public class BubbleController implements ConfigurationChangeListener {
mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
}
- if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) {
- mBubblePositioner.setUsePinnedLocation(true);
- } else {
- mBubblePositioner.setUsePinnedLocation(false);
- }
-
addToWindowManagerMaybe();
}
@@ -1918,13 +1919,6 @@ public class BubbleController implements ConfigurationChangeListener {
}
@Override
- public void setBubbleBarEnabled(boolean enabled) {
- mMainExecutor.execute(() -> {
- BubbleController.this.setBubbleBarEnabled(enabled);
- });
- }
-
- @Override
public void onNotificationPanelExpandedChanged(boolean expanded) {
mMainExecutor.execute(
() -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 6230d22ebe12..3fd09675a245 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -283,7 +283,7 @@ public class BubbleData {
}
boolean isShowingOverflow() {
- return mShowingOverflow && (isExpanded() || mPositioner.showingInTaskbar());
+ return mShowingOverflow && isExpanded();
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 57c7731e69ed..ecddbda0fff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.bubbles;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
@@ -226,6 +227,8 @@ public class BubbleExpandedView extends LinearLayout {
try {
options.setTaskAlwaysOnTop(true);
options.setLaunchedFromBubble(true);
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
Intent fillInIntent = new Intent();
// Apply flags to make behaviour match documentLaunchMode=always.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 07c58527a815..5ea2450114f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -18,9 +18,6 @@ package com.android.wm.shell.bubbles;
import static android.view.View.LAYOUT_DIRECTION_RTL;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -39,8 +36,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
-import java.lang.annotation.Retention;
-
/**
* Keeps track of display size, configuration, and specific bubble sizes. One place for all
* placement and positioning calculations to refer to.
@@ -50,15 +45,6 @@ public class BubblePositioner {
? "BubblePositioner"
: BubbleDebugConfig.TAG_BUBBLES;
- @Retention(SOURCE)
- @IntDef({TASKBAR_POSITION_NONE, TASKBAR_POSITION_RIGHT, TASKBAR_POSITION_LEFT,
- TASKBAR_POSITION_BOTTOM})
- @interface TaskbarPosition {}
- public static final int TASKBAR_POSITION_NONE = -1;
- public static final int TASKBAR_POSITION_RIGHT = 0;
- public static final int TASKBAR_POSITION_LEFT = 1;
- public static final int TASKBAR_POSITION_BOTTOM = 2;
-
/** When the bubbles are collapsed in a stack only some of them are shown, this is how many. **/
public static final int NUM_VISIBLE_WHEN_RESTING = 2;
/** Indicates a bubble's height should be the maximum available space. **/
@@ -108,15 +94,9 @@ public class BubblePositioner {
private int mOverflowHeight;
private int mMinimumFlyoutWidthLargeScreen;
- private PointF mPinLocation;
private PointF mRestingStackPosition;
private int[] mPaddings = new int[4];
- private boolean mShowingInTaskbar;
- private @TaskbarPosition int mTaskbarPosition = TASKBAR_POSITION_NONE;
- private int mTaskbarIconSize;
- private int mTaskbarSize;
-
public BubblePositioner(Context context, WindowManager windowManager) {
mContext = context;
mWindowManager = windowManager;
@@ -153,27 +133,11 @@ public class BubblePositioner {
+ " insets: " + insets
+ " isLargeScreen: " + mIsLargeScreen
+ " isSmallTablet: " + mIsSmallTablet
- + " bounds: " + bounds
- + " showingInTaskbar: " + mShowingInTaskbar);
+ + " bounds: " + bounds);
}
updateInternal(mRotation, insets, bounds);
}
- /**
- * Updates position information to account for taskbar state.
- *
- * @param taskbarPosition which position the taskbar is displayed in.
- * @param showingInTaskbar whether the taskbar is being shown.
- */
- public void updateForTaskbar(int iconSize,
- @TaskbarPosition int taskbarPosition, boolean showingInTaskbar, int taskbarSize) {
- mShowingInTaskbar = showingInTaskbar;
- mTaskbarIconSize = iconSize;
- mTaskbarPosition = taskbarPosition;
- mTaskbarSize = taskbarSize;
- update();
- }
-
@VisibleForTesting
public void updateInternal(int rotation, Insets insets, Rect bounds) {
mRotation = rotation;
@@ -232,10 +196,6 @@ public class BubblePositioner {
R.dimen.bubbles_flyout_min_width_large_screen);
mMaxBubbles = calculateMaxBubbles();
-
- if (mShowingInTaskbar) {
- adjustForTaskbar();
- }
}
/**
@@ -260,30 +220,6 @@ public class BubblePositioner {
return mDefaultMaxBubbles;
}
- /**
- * Taskbar insets appear as navigationBar insets, however, unlike navigationBar this should
- * not inset bubbles UI as bubbles floats above the taskbar. This adjust the available space
- * and insets to account for the taskbar.
- */
- // TODO(b/171559950): When the insets are reported correctly we can remove this logic
- private void adjustForTaskbar() {
- // When bar is showing on edges... subtract that inset because we appear on top
- if (mShowingInTaskbar && mTaskbarPosition != TASKBAR_POSITION_BOTTOM) {
- WindowInsets metricInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
- Insets navBarInsets = metricInsets.getInsetsIgnoringVisibility(
- WindowInsets.Type.navigationBars());
- int newInsetLeft = mInsets.left;
- int newInsetRight = mInsets.right;
- if (mTaskbarPosition == TASKBAR_POSITION_LEFT) {
- mPositionRect.left -= navBarInsets.left;
- newInsetLeft -= navBarInsets.left;
- } else if (mTaskbarPosition == TASKBAR_POSITION_RIGHT) {
- mPositionRect.right += navBarInsets.right;
- newInsetRight -= navBarInsets.right;
- }
- mInsets = Insets.of(newInsetLeft, mInsets.top, newInsetRight, mInsets.bottom);
- }
- }
/**
* @return a rect of available screen space accounting for orientation, system bars and cutouts.
@@ -327,14 +263,12 @@ public class BubblePositioner {
* to the left or right side.
*/
public boolean showBubblesVertically() {
- return isLandscape() || mShowingInTaskbar || mIsLargeScreen;
+ return isLandscape() || mIsLargeScreen;
}
/** Size of the bubble. */
public int getBubbleSize() {
- return (mShowingInTaskbar && mTaskbarIconSize > 0)
- ? mTaskbarIconSize
- : mBubbleSize;
+ return mBubbleSize;
}
/** The amount of padding at the top of the screen that the bubbles avoid when being placed. */
@@ -699,9 +633,6 @@ public class BubblePositioner {
/** The position the bubble stack should rest at when collapsed. */
public PointF getRestingPosition() {
- if (mPinLocation != null) {
- return mPinLocation;
- }
if (mRestingStackPosition == null) {
return getDefaultStartPosition();
}
@@ -713,9 +644,6 @@ public class BubblePositioner {
* is being shown.
*/
public PointF getDefaultStartPosition() {
- if (mPinLocation != null) {
- return mPinLocation;
- }
// Start on the left if we're in LTR, right otherwise.
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
@@ -730,7 +658,6 @@ public class BubblePositioner {
1 /* default starts with 1 bubble */));
}
-
/**
* Returns the region that the stack position must stay within. This goes slightly off the left
* and right sides of the screen, below the status bar/cutout and above the navigation bar.
@@ -751,39 +678,6 @@ public class BubblePositioner {
}
/**
- * @return whether the bubble stack is pinned to the taskbar.
- */
- public boolean showingInTaskbar() {
- return mShowingInTaskbar;
- }
-
- /**
- * @return the taskbar position if set.
- */
- public int getTaskbarPosition() {
- return mTaskbarPosition;
- }
-
- public int getTaskbarSize() {
- return mTaskbarSize;
- }
-
- /**
- * In some situations bubbles will be pinned to a specific onscreen location. This sets whether
- * bubbles should be pinned or not.
- */
- public void setUsePinnedLocation(boolean usePinnedLocation) {
- if (usePinnedLocation) {
- mShowingInTaskbar = true;
- mPinLocation = new PointF(mPositionRect.right - mBubbleSize,
- mPositionRect.bottom - mBubbleSize);
- } else {
- mPinLocation = null;
- mShowingInTaskbar = false;
- }
- }
-
- /**
* Navigation bar has an area where system gestures can be started from.
*
* @return {@link Rect} for system navigation bar gesture zone
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 abe42eec7061..7d71089ef4fa 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
@@ -680,8 +680,6 @@ public class BubbleStackView extends FrameLayout
// Re-show the expanded view if we hid it.
showExpandedViewIfNeeded();
- } else if (mPositioner.showingInTaskbar()) {
- mStackAnimationController.snapStackBack();
} else {
// Fling the stack to the edge, and save whether or not it's going to end up on
// the left side of the screen.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 1753cda895fe..4c0a93fb9355 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -273,11 +273,6 @@ public interface Bubbles {
*/
void onUserRemoved(int removedUserId);
- /**
- * Sets whether bubble bar should be enabled or not.
- */
- void setBubbleBarEnabled(boolean enabled);
-
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0ee0ea60a1bc..5533842f2d89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -417,23 +417,9 @@ public class StackAnimationController extends
}
/**
- * Snaps the stack back to the previous resting position.
- */
- public void snapStackBack() {
- if (mLayout == null) {
- return;
- }
- PointF p = getStackPositionAlongNearestHorizontalEdge();
- springStackAfterFling(p.x, p.y);
- }
-
- /**
* Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
*/
public PointF getStackPositionAlongNearestHorizontalEdge() {
- if (mPositioner.showingInTaskbar()) {
- return mPositioner.getRestingPosition();
- }
final PointF stackPos = getStackPosition();
final boolean onLeft = mLayout.isFirstChildXLeftOfCenter(stackPos.x);
final RectF bounds = mPositioner.getAllowableStackPositionRegion(getBubbleCount());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index 480bf93b2ddb..53bf42a3c911 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -39,6 +39,9 @@ import android.window.TaskSnapshot;
* Represents the content overlay used during the entering PiP animation.
*/
public abstract class PipContentOverlay {
+ // Fixed string used in WMShellFlickerTests
+ protected static final String LAYER_NAME = "PipContentOverlay";
+
protected SurfaceControl mLeash;
/** Attaches the internal {@link #mLeash} to the given parent leash. */
@@ -86,7 +89,7 @@ public abstract class PipContentOverlay {
mContext = context;
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.setColorLayer()
.build();
}
@@ -139,7 +142,7 @@ public abstract class PipContentOverlay {
mSourceRectHint = new Rect(sourceRectHint);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
@@ -194,7 +197,7 @@ public abstract class PipContentOverlay {
prepareAppIconOverlay(activityInfo);
mLeash = new SurfaceControl.Builder(new SurfaceSession())
.setCallsite(TAG)
- .setName(TAG)
+ .setName(LAYER_NAME)
.build();
}
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 f11836ea5bee..e9d257139779 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
@@ -1594,7 +1594,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// source rect hint to enter PiP use bounds animation.
if (sourceHintRect == null) {
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
mContext, currentBounds, mTaskInfo.topActivityInfo);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6b0337d3fb4a..a91a3424f3a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -804,7 +804,7 @@ public class PipTransition extends PipTransitionController {
// We use content overlay when there is no source rect hint to enter PiP use bounds
// animation.
if (SystemProperties.getBoolean(
- "persist.wm.debug.enable_pip_app_icon_overlay", false)) {
+ "persist.wm.debug.enable_pip_app_icon_overlay", true)) {
animator.setAppIconContentOverlay(
mContext, currentBounds, taskInfo.topActivityInfo);
} else {
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 225258773013..146abea2bc31 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
@@ -204,7 +204,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// and exit, since exit itself can trigger a number of changes that update the stages.
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
- private boolean mIsSplitEntering;
+ private boolean mIsDividerRemoteAnimating;
private boolean mIsDropEntering;
private boolean mIsExiting;
@@ -764,17 +764,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (pendingIntent2 == null) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- if (shortcutInfo1 != null) {
- wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
- } else {
- wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
- }
- mSyncQueue.queue(wct);
+ // Launching a solo intent or shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent1, fillInIntent1, shortcutInfo1,
+ options1, adapter, wct);
return;
}
@@ -797,13 +789,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo intent as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(pendingIntent, fillInIntent, null, options1,
+ adapter, wct);
return;
}
@@ -822,13 +810,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (options1 == null) options1 = new Bundle();
if (taskId == INVALID_TASK_ID) {
- // Launching a solo task.
- ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
- activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
- options1 = activityOptions.toBundle();
- addActivityOptions(options1, null /* launchTarget */);
- wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
- mSyncQueue.queue(wct);
+ // Launching a solo shortcut as fullscreen.
+ launchAsFullscreenWithRemoteAnimation(null, null, shortcutInfo, options1, adapter, wct);
return;
}
@@ -838,6 +821,49 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
instanceId);
}
+ private void launchAsFullscreenWithRemoteAnimation(@Nullable PendingIntent pendingIntent,
+ @Nullable Intent fillInIntent, @Nullable ShortcutInfo shortcutInfo,
+ @Nullable Bundle options, RemoteAnimationAdapter adapter,
+ WindowContainerTransaction wct) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ }
+ }
+ t.apply();
+
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ addActivityOptions(options, null /* launchTarget */);
+ if (shortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options);
+ } else if (pendingIntent != null) {
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options);
+ } else {
+ Slog.e(TAG, "Pending intent and shortcut are null is invalid case.");
+ }
+ mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
+ }
+
private void startWithLegacyTransition(WindowContainerTransaction wct,
@Nullable PendingIntent mainPendingIntent, @Nullable Intent mainFillInIntent,
@Nullable ShortcutInfo mainShortcutInfo, @Nullable Bundle mainOptions,
@@ -881,7 +907,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Set false to avoid record new bounds with old task still on top;
mShouldUpdateRecents = false;
- mIsSplitEntering = true;
+ mIsDividerRemoteAnimating = true;
if (mSplitRequest == null) {
mSplitRequest = new SplitRequest(mainTaskId,
mainPendingIntent != null ? mainPendingIntent.getIntent() : null,
@@ -894,23 +920,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (options == null) options = new Bundle();
addActivityOptions(options, mMainStage);
- options = wrapAsSplitRemoteAnimation(adapter, options);
updateWindowBounds(mSplitLayout, wct);
+ wct.reorder(mRootTaskInfo.token, true);
+ wct.setForceTranslucent(mRootTaskInfo.token, false);
// TODO(b/268008375): Merge APIs to start a split pair into one.
if (mainTaskId != INVALID_TASK_ID) {
+ options = wrapAsSplitRemoteAnimation(adapter, options);
wct.startTask(mainTaskId, options);
- } else if (mainShortcutInfo != null) {
- wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ mSyncQueue.queue(wct);
} else {
- wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ if (mainShortcutInfo != null) {
+ wct.startShortcut(mContext.getPackageName(), mainShortcutInfo, options);
+ } else {
+ wct.sendPendingIntent(mainPendingIntent, mainFillInIntent, options);
+ }
+ mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
}
- wct.reorder(mRootTaskInfo.token, true);
- wct.setForceTranslucent(mRootTaskInfo.token, false);
-
- mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setDividerVisibility(true, t);
});
@@ -967,6 +995,54 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return activityOptions.toBundle();
}
+ private LegacyTransitions.ILegacyTransition wrapAsSplitRemoteAnimation(
+ RemoteAnimationAdapter adapter) {
+ LegacyTransitions.ILegacyTransition transition =
+ (transit, apps, wallpapers, nonApps, finishedCallback, t) -> {
+ if (apps == null || apps.length == 0) {
+ onRemoteAnimationFinished(apps);
+ t.apply();
+ try {
+ adapter.getRunner().onAnimationCancelled(mKeyguardShowing);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ return;
+ }
+
+ // Wrap the divider bar into non-apps target to animate together.
+ nonApps = ArrayUtils.appendElement(RemoteAnimationTarget.class, nonApps,
+ getDividerBarLegacyTarget());
+
+ for (int i = 0; i < apps.length; ++i) {
+ if (apps[i].mode == MODE_OPENING) {
+ t.show(apps[i].leash);
+ // Reset the surface position of the opening app to prevent offset.
+ t.setPosition(apps[i].leash, 0, 0);
+ }
+ }
+ t.apply();
+
+ IRemoteAnimationFinishedCallback wrapCallback =
+ new IRemoteAnimationFinishedCallback.Stub() {
+ @Override
+ public void onAnimationFinished() throws RemoteException {
+ onRemoteAnimationFinished(apps);
+ finishedCallback.onAnimationFinished();
+ }
+ };
+ Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication());
+ try {
+ adapter.getRunner().onAnimationStart(
+ transit, apps, wallpapers, nonApps, wrapCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error starting remote animation", e);
+ }
+ };
+
+ return transition;
+ }
+
private void setEnterInstanceId(InstanceId instanceId) {
if (instanceId != null) {
mLogger.enterRequested(instanceId, ENTER_REASON_LAUNCHER);
@@ -974,7 +1050,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
mSplitRequest = null;
// If any stage has no child after animation finished, it means that split will display
@@ -993,6 +1069,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ private void onRemoteAnimationFinished(RemoteAnimationTarget[] apps) {
+ mIsDividerRemoteAnimating = false;
+ mShouldUpdateRecents = true;
+ mSplitRequest = null;
+ // If any stage has no child after finished animation, that side of the split will display
+ // nothing. This might happen if starting the same app on the both sides while not
+ // supporting multi-instance. Exit the split screen and expand that app to full screen.
+ if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ mMainExecutor.execute(() -> exitSplitScreen(mMainStage.getChildCount() == 0
+ ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
+ mSplitUnsupportedToast.show();
+ return;
+ }
+
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_TOP_OR_LEFT, apps, evictWct);
+ prepareEvictNonOpeningChildTasks(SPLIT_POSITION_BOTTOM_OR_RIGHT, apps, evictWct);
+ mSyncQueue.queue(evictWct);
+ }
+
+
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
@@ -1247,7 +1344,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
});
mShouldUpdateRecents = false;
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mSplitLayout.getInvisibleBounds(mTempRect1);
if (childrenToTop == null || childrenToTop.getTopVisibleChildTaskId() == INVALID_TASK_ID) {
@@ -1590,7 +1687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
&& !ENABLE_SHELL_TRANSITIONS) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
- mIsSplitEntering = false;
+ mIsDividerRemoteAnimating = false;
mSplitLayout.update(null /* t */);
onLayoutSizeChanged(mSplitLayout);
}
@@ -1640,9 +1737,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
void onChildTaskAppeared(StageListenerImpl stageListener, int taskId) {
+ // Handle entering split screen while there is a split pair running in the background.
if (stageListener == mSideStageListener && !isSplitScreenVisible() && isSplitActive()
- && !mIsSplitEntering) {
- // Handle entring split case here if split already running background.
+ && mSplitRequest == null) {
if (mIsDropEntering) {
mSplitLayout.resetDividerPosition();
} else {
@@ -1734,7 +1831,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerVisible = visible;
sendSplitVisibilityChanged();
- if (mIsSplitEntering) {
+ if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
@@ -1754,7 +1851,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
" Skip animating divider bar due to divider leash not ready.");
return;
}
- if (mIsSplitEntering) {
+ if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
" Skip animating divider bar due to it's remote animating.");
return;
@@ -1822,7 +1919,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.flingDividerToDismiss(
mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
EXIT_REASON_APP_FINISHED);
- } else if (!isSplitScreenVisible() && !mIsSplitEntering) {
+ } else if (!isSplitScreenVisible() && mSplitRequest == null) {
+ // Dismiss split screen in the background once any sides of the split become empty.
exitSplitScreen(null /* childrenToTop */, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && hasChildren && !mMainStage.isActive()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index ef405c858e3d..75112b62c1c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -78,7 +78,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
int mAnimType = 0;
final IBinder mTransition;
- Transitions.TransitionFinishCallback mFinishCallback = null;
Transitions.TransitionHandler mLeftoversHandler = null;
WindowContainerTransaction mFinishWCT = null;
@@ -241,20 +240,25 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
}
if (pipChange == null) {
if (mixed.mLeftoversHandler != null) {
- return mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info,
- startTransaction, finishTransaction, finishCallback);
+ if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition,
+ info, startTransaction, finishTransaction, (wct, wctCB) -> {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct, wctCB);
+ })) {
+ return true;
+ }
}
+ mActiveTransitions.remove(mixed);
return false;
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate"
+ " animation because remote-animation likely doesn't support it");
- mixed.mFinishCallback = finishCallback;
Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
--mixed.mInFlightSubAnimations;
mixed.joinFinishArgs(wct, wctCB);
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
- mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
};
// Split the transition into 2 parts: the pip part and the rest.
mixed.mInFlightSubAnimations = 2;
@@ -304,10 +308,10 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
}
if (pipChange == null) {
// um, something probably went wrong.
+ mActiveTransitions.remove(mixed);
return false;
}
final boolean isGoingHome = homeIsOpening;
- mixed.mFinishCallback = finishCallback;
Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
--mixed.mInFlightSubAnimations;
mixed.joinFinishArgs(wct, wctCB);
@@ -316,7 +320,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
if (isGoingHome) {
mSplitHandler.onTransitionAnimationComplete();
}
- mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
};
if (isGoingHome) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
@@ -408,7 +412,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
unlinkMissingParents(everythingElse);
final MixedTransition mixed = new MixedTransition(
MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE, transition);
- mixed.mFinishCallback = finishCallback;
mActiveTransitions.add(mixed);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is a mix of display change "
+ "and split change.");
@@ -420,7 +423,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
mixed.joinFinishArgs(wct, wctCB);
if (mixed.mInFlightSubAnimations > 0) return;
mActiveTransitions.remove(mixed);
- mixed.mFinishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
+ finishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
};
// Dispatch the display change. This will most-likely be taken by the default handler.
@@ -447,7 +450,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
// Already done, so no need to end it.
return;
}
- if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
+ // queue since no actual animation.
+ } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
boolean ended = mSplitHandler.end();
// If split couldn't end (because it is remote), then don't end everything else
@@ -461,8 +466,12 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler {
} else {
mPipHandler.end();
}
- } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
- // queue
+ } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+ mPipHandler.end();
+ if (mixed.mLeftoversHandler != null) {
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ }
} else {
throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ mixed.mType);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
new file mode 100644
index 000000000000..0386ec38a3ff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -0,0 +1,65 @@
+/*
+ * 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.transition;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import java.util.ArrayList;
+
+/**
+ * A Simple handler that tracks SLEEP transitions. We track them specially since we (ab)use these
+ * as sentinels for fast-forwarding through animations when the screen is off.
+ *
+ * There should only be one SleepHandler and it is used explicitly by {@link Transitions} so we
+ * don't register it like a normal handler.
+ */
+class SleepHandler implements Transitions.TransitionHandler {
+ final ArrayList<IBinder> mSleepTransitions = new ArrayList<>();
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null, null);
+ mSleepTransitions.remove(transition);
+ return true;
+ }
+
+ @Override
+ @Nullable
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ mSleepTransitions.add(transition);
+ return new WindowContainerTransaction();
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ Log.e(Transitions.TAG, "Sleep transition was consumed. This doesn't make sense");
+ mSleepTransitions.remove(transition);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index 8d29901c3a07..bcc37baa5b00 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -143,8 +143,12 @@ public class TransitionAnimationHelper {
Animation a = null;
if (animAttr != 0) {
if (overrideType == ANIM_FROM_STYLE && !isTask) {
- a = loadCustomActivityTransition(animAttr, options, enter, transitionAnimation);
- if (a == null) {
+ final TransitionInfo.AnimationOptions.CustomActivityTransition customTransition =
+ getCustomActivityTransition(animAttr, options);
+ if (customTransition != null) {
+ a = loadCustomActivityTransition(
+ customTransition, options, enter, transitionAnimation);
+ } else {
a = transitionAnimation
.loadAnimationAttr(options.getPackageName(), options.getAnimations(),
animAttr, translucent);
@@ -161,10 +165,8 @@ public class TransitionAnimationHelper {
return a;
}
- static Animation loadCustomActivityTransition(int animAttr,
- TransitionInfo.AnimationOptions options, boolean enter,
- TransitionAnimation transitionAnimation) {
- Animation a = null;
+ static TransitionInfo.AnimationOptions.CustomActivityTransition getCustomActivityTransition(
+ int animAttr, TransitionInfo.AnimationOptions options) {
boolean isOpen = false;
switch (animAttr) {
case R.styleable.WindowAnimation_activityOpenEnterAnimation:
@@ -178,17 +180,19 @@ public class TransitionAnimationHelper {
return null;
}
- final TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim =
- options.getCustomActivityTransition(isOpen);
- if (transitionAnim != null) {
- a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(),
- enter ? transitionAnim.getCustomEnterResId()
- : transitionAnim.getCustomExitResId());
- if (a != null && transitionAnim.getCustomBackgroundColor() != 0) {
- a.setBackdropColor(transitionAnim.getCustomBackgroundColor());
- }
- }
+ return options.getCustomActivityTransition(isOpen);
+ }
+ static Animation loadCustomActivityTransition(
+ @NonNull TransitionInfo.AnimationOptions.CustomActivityTransition transitionAnim,
+ TransitionInfo.AnimationOptions options, boolean enter,
+ TransitionAnimation transitionAnimation) {
+ final Animation a = transitionAnimation.loadAppTransitionAnimation(options.getPackageName(),
+ enter ? transitionAnim.getCustomEnterResId()
+ : transitionAnim.getCustomExitResId());
+ if (a != null && transitionAnim.getCustomBackgroundColor() != 0) {
+ a.setBackdropColor(transitionAnim.getCustomBackgroundColor());
+ }
return a;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 3b154d1cda83..155990a40836 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
@@ -124,6 +125,7 @@ public class Transitions implements RemoteCallable<Transitions> {
private final DisplayController mDisplayController;
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
+ private final SleepHandler mSleepHandler = new SleepHandler();
private boolean mIsRegistered = false;
@@ -137,6 +139,14 @@ public class Transitions implements RemoteCallable<Transitions> {
private float mTransitionAnimationScaleSetting = 1.0f;
+ /**
+ * How much time we allow for an animation to finish itself on sleep. If it takes longer, we
+ * will force-finish it (on this end) which may leave it in a bad state but won't hang the
+ * device. This needs to be pretty small because it is an allowance for each queued animation,
+ * however it can't be too small since there is some potential IPC involved.
+ */
+ private static final int SLEEP_ALLOWANCE_MS = 120;
+
private static final class ActiveTransition {
IBinder mToken;
TransitionHandler mHandler;
@@ -478,11 +488,29 @@ public class Transitions implements RemoteCallable<Transitions> {
+ Arrays.toString(mActiveTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
+ final ActiveTransition active = mActiveTransitions.get(activeIdx);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
}
+ if (info.getType() == TRANSIT_SLEEP) {
+ if (activeIdx > 0) {
+ active.mInfo = info;
+ active.mStartT = t;
+ active.mFinishT = finishT;
+ if (!info.getRootLeash().isValid()) {
+ // Shell has some debug settings which makes calling binders with invalid
+ // surfaces crash, so replace it with a "real" one.
+ info.setRootLeash(new SurfaceControl.Builder().setName("Invalid")
+ .setContainerLayer().build(), 0, 0);
+ }
+ // Sleep starts a process of forcing all prior transitions to finish immediately
+ finishForSleep(null /* forceFinish */);
+ return;
+ }
+ }
+
// Allow to notify keyguard un-occluding state to KeyguardService, which can happen while
// screen-off, so there might no visibility change involved.
if (!info.getRootLeash().isValid() && info.getType() != TRANSIT_KEYGUARD_UNOCCLUDE) {
@@ -527,7 +555,6 @@ public class Transitions implements RemoteCallable<Transitions> {
return;
}
- final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
@@ -772,6 +799,12 @@ public class Transitions implements RemoteCallable<Transitions> {
++mergeIdx;
continue;
}
+ if (mergeCandidate.mInfo == null) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition merge candidate"
+ + " %s is not ready yet", mergeCandidate.mToken);
+ // The later transition should not be merged if the prior one is not ready.
+ return;
+ }
if (mergeCandidate.mMerged) {
throw new IllegalStateException("Can't merge a transition after not-merging"
+ " a preceding one.");
@@ -797,23 +830,30 @@ public class Transitions implements RemoteCallable<Transitions> {
}
final ActiveTransition active = new ActiveTransition();
WindowContainerTransaction wct = null;
- for (int i = mHandlers.size() - 1; i >= 0; --i) {
- wct = mHandlers.get(i).handleRequest(transitionToken, request);
- if (wct != null) {
- active.mHandler = mHandlers.get(i);
- break;
+
+ // If we have sleep, we use a special handler and we try to finish everything ASAP.
+ if (request.getType() == TRANSIT_SLEEP) {
+ mSleepHandler.handleRequest(transitionToken, request);
+ active.mHandler = mSleepHandler;
+ } else {
+ for (int i = mHandlers.size() - 1; i >= 0; --i) {
+ wct = mHandlers.get(i).handleRequest(transitionToken, request);
+ if (wct != null) {
+ active.mHandler = mHandlers.get(i);
+ break;
+ }
}
- }
- if (request.getDisplayChange() != null) {
- TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
- if (change.getEndRotation() != change.getStartRotation()) {
- // Is a rotation, so dispatch to all displayChange listeners
- if (wct == null) {
- wct = new WindowContainerTransaction();
+ if (request.getDisplayChange() != null) {
+ TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
+ if (change.getEndRotation() != change.getStartRotation()) {
+ // Is a rotation, so dispatch to all displayChange listeners
+ if (wct == null) {
+ wct = new WindowContainerTransaction();
+ }
+ mDisplayController.getChangeController().dispatchOnDisplayChange(wct,
+ change.getDisplayId(), change.getStartRotation(),
+ change.getEndRotation(), null /* newDisplayAreaInfo */);
}
- mDisplayController.getChangeController().dispatchOnDisplayChange(wct,
- change.getDisplayId(), change.getStartRotation(), change.getEndRotation(),
- null /* newDisplayAreaInfo */);
}
}
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
@@ -840,6 +880,56 @@ public class Transitions implements RemoteCallable<Transitions> {
}
/**
+ * Finish running animations (almost) immediately when a SLEEP transition comes in. We use this
+ * as both a way to reduce unnecessary work (animations not visible while screen off) and as a
+ * failsafe to unblock "stuck" animations (in particular remote animations).
+ *
+ * This works by "merging" the sleep transition into the currently-playing transition (even if
+ * its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish
+ * within `SLEEP_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
+ * send an abort/consumed message).
+ *
+ * This is then repeated until there are no more pending sleep transitions.
+ *
+ * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
+ * signal to -- so it will be force-finished if it's still running.
+ */
+ private void finishForSleep(@Nullable IBinder forceFinish) {
+ if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) {
+ return;
+ }
+ if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) {
+ Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
+ + mActiveTransitions.get(0).mToken);
+ onFinish(mActiveTransitions.get(0).mToken, null, null, true);
+ }
+ final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
+ while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
+ final ActiveTransition playing = mActiveTransitions.get(0);
+ int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0));
+ if (sleepIdx >= 0) {
+ // Try to signal that we are sleeping by attempting to merge the sleep transition
+ // into the playing one.
+ final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx);
+ playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
+ playing.mToken, (wct, cb) -> {});
+ } else {
+ Log.e(TAG, "Couldn't find sleep transition in active list: "
+ + mSleepHandler.mSleepTransitions.get(0));
+ }
+ // it's possible to complete immediately. If that happens, just repeat the signal
+ // loop until we either finish everything or start playing an animation that isn't
+ // finishing immediately.
+ if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
+ // Give it a (very) short amount of time to process it before forcing.
+ mMainExecutor.executeDelayed(
+ () -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS);
+ break;
+ }
+ }
+ }
+
+ /**
* Interface for a callback that must be called after a TransitionHandler finishes playing an
* animation.
*/
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index da80c6f46976..5c9920970761 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -80,7 +80,8 @@ class EnterSplitScreenByDragFromTaskbar(flicker: FlickerTest) : SplitScreenBase(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
- fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+ fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false,
+ appExistAtStart = false)
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 6c9b186b7ede..e63bbeb05575 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -563,6 +563,33 @@ public class ShellTransitionTests extends ShellTestCase {
assertEquals(0, mDefaultHandler.activeCount());
}
+
+ @Test
+ public void testTransitionMergingOnFinish() {
+ final Transitions transitions = createTestTransitions();
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ // The current transition.
+ final IBinder transitToken1 = new Binder();
+ requestStartTransition(transitions, transitToken1);
+ onTransitionReady(transitions, transitToken1);
+
+ // The next ready transition.
+ final IBinder transitToken2 = new Binder();
+ requestStartTransition(transitions, transitToken2);
+ onTransitionReady(transitions, transitToken2);
+
+ // The non-ready merge candidate.
+ final IBinder transitTokenNotReady = new Binder();
+ requestStartTransition(transitions, transitTokenNotReady);
+
+ mDefaultHandler.setSimulateMerge(true);
+ mDefaultHandler.mFinishes.get(0).onTransitionFinished(null /* wct */, null /* wctCB */);
+
+ // Make sure that the non-ready transition is not merged.
+ assertEquals(0, mDefaultHandler.mergeCount());
+ }
+
@Test
public void testTransitionOrderMatchesCore() {
Transitions transitions = createTestTransitions();
@@ -1036,6 +1063,21 @@ public class ShellTransitionTests extends ShellTestCase {
}
}
+ private static void requestStartTransition(Transitions transitions, IBinder token) {
+ transitions.requestStartTransition(token,
+ new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+ }
+
+ private static void onTransitionReady(Transitions transitions, IBinder token) {
+ transitions.onTransitionReady(token, createTransitionInfo(),
+ mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class));
+ }
+
+ private static TransitionInfo createTransitionInfo() {
+ return new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ }
+
private static SurfaceControl createMockSurface(boolean valid) {
SurfaceControl sc = mock(SurfaceControl.class);
doReturn(valid).when(sc).isValid();
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
index 5ecec4ddd1ad..3125f088c72b 100644
--- a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightDreamManager.java
@@ -72,6 +72,7 @@ public final class LowLightDreamManager {
public static final int AMBIENT_LIGHT_MODE_LOW_LIGHT = 2;
private final DreamManager mDreamManager;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
@Nullable
private final ComponentName mLowLightDreamComponent;
@@ -81,8 +82,10 @@ public final class LowLightDreamManager {
@Inject
public LowLightDreamManager(
DreamManager dreamManager,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
@Named(LOW_LIGHT_DREAM_COMPONENT) @Nullable ComponentName lowLightDreamComponent) {
mDreamManager = dreamManager;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mLowLightDreamComponent = lowLightDreamComponent;
}
@@ -111,7 +114,9 @@ public final class LowLightDreamManager {
mAmbientLightMode = ambientLightMode;
- mDreamManager.setSystemDreamComponent(mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT
- ? mLowLightDreamComponent : null);
+ boolean shouldEnterLowLight = mAmbientLightMode == AMBIENT_LIGHT_MODE_LOW_LIGHT;
+ mLowLightTransitionCoordinator.notifyBeforeLowLightTransition(shouldEnterLowLight,
+ () -> mDreamManager.setSystemDreamComponent(
+ shouldEnterLowLight ? mLowLightDreamComponent : null));
}
}
diff --git a/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
new file mode 100644
index 000000000000..874a2d5af75e
--- /dev/null
+++ b/libs/dream/lowlight/src/com/android/dream/lowlight/LowLightTransitionCoordinator.java
@@ -0,0 +1,111 @@
+/*
+ * 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.dream.lowlight;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class that allows listening and running animations before entering or exiting low light.
+ */
+@Singleton
+public class LowLightTransitionCoordinator {
+ /**
+ * Listener that is notified before low light entry.
+ */
+ public interface LowLightEnterListener {
+ /**
+ * Callback that is notified before the device enters low light.
+ *
+ * @return an optional animator that will be waited upon before entering low light.
+ */
+ Animator onBeforeEnterLowLight();
+ }
+
+ /**
+ * Listener that is notified before low light exit.
+ */
+ public interface LowLightExitListener {
+ /**
+ * Callback that is notified before the device exits low light.
+ *
+ * @return an optional animator that will be waited upon before exiting low light.
+ */
+ Animator onBeforeExitLowLight();
+ }
+
+ private LowLightEnterListener mLowLightEnterListener;
+ private LowLightExitListener mLowLightExitListener;
+
+ @Inject
+ public LowLightTransitionCoordinator() {
+ }
+
+ /**
+ * Sets the listener for the low light enter event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightEnterListener(@Nullable LowLightEnterListener lowLightEnterListener) {
+ mLowLightEnterListener = lowLightEnterListener;
+ }
+
+ /**
+ * Sets the listener for the low light exit event.
+ *
+ * Only one listener can be set at a time. This method will overwrite any previously set
+ * listener. Null can be used to unset the listener.
+ */
+ public void setLowLightExitListener(@Nullable LowLightExitListener lowLightExitListener) {
+ mLowLightExitListener = lowLightExitListener;
+ }
+
+ /**
+ * Notifies listeners that the device is about to enter or exit low light.
+ *
+ * @param entering true if listeners should be notified before entering low light, false if this
+ * is notifying before exiting.
+ * @param callback callback that will be run after listeners complete.
+ */
+ void notifyBeforeLowLightTransition(boolean entering, Runnable callback) {
+ Animator animator = null;
+
+ if (entering && mLowLightEnterListener != null) {
+ animator = mLowLightEnterListener.onBeforeEnterLowLight();
+ } else if (!entering && mLowLightExitListener != null) {
+ animator = mLowLightExitListener.onBeforeExitLowLight();
+ }
+
+ // If the listener returned an animator to indicate it was running an animation, run the
+ // callback after the animation completes, otherwise call the callback directly.
+ if (animator != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ callback.run();
+ }
+ });
+ } else {
+ callback.run();
+ }
+ }
+}
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
index 91a170f7ae14..4b95d8c84bac 100644
--- a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightDreamManagerTest.java
@@ -21,7 +21,10 @@ import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE
import static com.android.dream.lowlight.LowLightDreamManager.AMBIENT_LIGHT_MODE_UNKNOWN;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -44,44 +47,52 @@ public class LowLightDreamManagerTest {
private DreamManager mDreamManager;
@Mock
+ private LowLightTransitionCoordinator mTransitionCoordinator;
+
+ @Mock
private ComponentName mDreamComponent;
+ LowLightDreamManager mLowLightDreamManager;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ // Automatically run any provided Runnable to mTransitionCoordinator to simplify testing.
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(1)).run();
+ return null;
+ }).when(mTransitionCoordinator).notifyBeforeLowLightTransition(anyBoolean(),
+ any(Runnable.class));
+
+ mLowLightDreamManager = new LowLightDreamManager(mDreamManager, mTransitionCoordinator,
+ mDreamComponent);
}
@Test
public void setAmbientLightMode_lowLight_setSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(true), any());
verify(mDreamManager).setSystemDreamComponent(mDreamComponent);
}
@Test
public void setAmbientLightMode_regularLight_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_REGULAR);
+ verify(mTransitionCoordinator).notifyBeforeLowLightTransition(eq(false), any());
verify(mDreamManager).setSystemDreamComponent(null);
}
@Test
public void setAmbientLightMode_defaultUnknownMode_clearSystemDream() {
- final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- mDreamComponent);
-
// Set to low light first.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
clearInvocations(mDreamManager);
// Return to default unknown mode.
- lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
+ mLowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_UNKNOWN);
verify(mDreamManager).setSystemDreamComponent(null);
}
@@ -89,7 +100,7 @@ public class LowLightDreamManagerTest {
@Test
public void setAmbientLightMode_dreamComponentNotSet_doNothing() {
final LowLightDreamManager lowLightDreamManager = new LowLightDreamManager(mDreamManager,
- null /*dream component*/);
+ mTransitionCoordinator, null /*dream component*/);
lowLightDreamManager.setAmbientLightMode(AMBIENT_LIGHT_MODE_LOW_LIGHT);
diff --git a/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
new file mode 100644
index 000000000000..81e1e33d6220
--- /dev/null
+++ b/libs/dream/lowlight/tests/src/com.android.dream.lowlight/LowLightTransitionCoordinatorTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.dream.lowlight;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class LowLightTransitionCoordinatorTest {
+ @Mock
+ private LowLightTransitionCoordinator.LowLightEnterListener mEnterListener;
+
+ @Mock
+ private LowLightTransitionCoordinator.LowLightExitListener mExitListener;
+
+ @Mock
+ private Animator mAnimator;
+
+ @Captor
+ private ArgumentCaptor<Animator.AnimatorListener> mAnimatorListenerCaptor;
+
+ @Mock
+ private Runnable mRunnable;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void onEnterCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verify(mEnterListener).onBeforeEnterLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void onExitCalledOnListeners() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightExitListener(mExitListener);
+
+ coordinator.notifyBeforeLowLightTransition(false, mRunnable);
+
+ verify(mExitListener).onBeforeExitLowLight();
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void listenerNotCalledAfterRemoval() {
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+
+ coordinator.setLowLightEnterListener(mEnterListener);
+ coordinator.setLowLightEnterListener(null);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ verifyZeroInteractions(mEnterListener);
+ verify(mRunnable).run();
+ }
+
+ @Test
+ public void runnableCalledAfterAnimationEnds() {
+ when(mEnterListener.onBeforeEnterLowLight()).thenReturn(mAnimator);
+
+ LowLightTransitionCoordinator coordinator = new LowLightTransitionCoordinator();
+ coordinator.setLowLightEnterListener(mEnterListener);
+
+ coordinator.notifyBeforeLowLightTransition(true, mRunnable);
+
+ // Animator listener is added and the runnable is not run yet.
+ verify(mAnimator).addListener(mAnimatorListenerCaptor.capture());
+ verifyZeroInteractions(mRunnable);
+
+ // Runnable is run once the animation ends.
+ mAnimatorListenerCaptor.getValue().onAnimationEnd(null);
+ verify(mRunnable).run();
+ }
+}
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 536bb49675f1..7228b895ebc4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -338,6 +338,7 @@ cc_defaults {
"jni/android_util_PathParser.cpp",
"jni/Bitmap.cpp",
+ "jni/BufferUtils.cpp",
"jni/HardwareBufferHelpers.cpp",
"jni/BitmapFactory.cpp",
"jni/ByteBufferStreamAdaptor.cpp",
@@ -374,6 +375,7 @@ cc_defaults {
"jni/text/LineBreaker.cpp",
"jni/text/MeasuredText.cpp",
"jni/text/TextShaper.cpp",
+ "jni/text/GraphemeBreak.cpp",
],
header_libs: [
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 983681707415..13e3c8e7bf77 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -104,33 +104,31 @@ private:
class Mesh {
public:
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
- size_t vertexBufferSize, jint vertexCount, jint vertexOffset,
+ Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
+ std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
: mMeshSpec(meshSpec)
, mMode(mode)
+ , mVertexBufferData(std::move(vertexBufferData))
, mVertexCount(vertexCount)
, mVertexOffset(vertexOffset)
, mBuilder(std::move(builder))
- , mBounds(bounds) {
- copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
- }
+ , mBounds(bounds) {}
- Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
- size_t vertexBufferSize, jint vertexCount, jint vertexOffset, const void* indexBuffer,
- size_t indexBufferSize, jint indexCount, jint indexOffset,
+ Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
+ std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
+ std::vector<uint8_t>&& indexBuffer, jint indexCount, jint indexOffset,
std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
: mMeshSpec(meshSpec)
, mMode(mode)
+ , mVertexBufferData(std::move(vertexBufferData))
, mVertexCount(vertexCount)
, mVertexOffset(vertexOffset)
+ , mIndexBufferData(std::move(indexBuffer))
, mIndexCount(indexCount)
, mIndexOffset(indexOffset)
, mBuilder(std::move(builder))
- , mBounds(bounds) {
- copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
- copyToVector(mIndexBufferData, indexBuffer, indexBufferSize);
- }
+ , mBounds(bounds) {}
Mesh(Mesh&&) = default;
@@ -180,13 +178,6 @@ public:
MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }
private:
- void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
- if (src) {
- dst.resize(srcSize);
- memcpy(dst.data(), src, srcSize);
- }
- }
-
sk_sp<SkMeshSpecification> mMeshSpec;
int mMode = 0;
diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp
index b7a15633ff6d..770822a049b7 100644
--- a/libs/hwui/apex/LayoutlibLoader.cpp
+++ b/libs/hwui/apex/LayoutlibLoader.cpp
@@ -66,6 +66,7 @@ extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_TextShaper(JNIEnv* env);
+extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env);
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_view_DisplayListCanvas(JNIEnv* env);
@@ -125,6 +126,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
{"android.graphics.text.MeasuredText",
REG_JNI(register_android_graphics_text_MeasuredText)},
{"android.graphics.text.TextRunShaper", REG_JNI(register_android_graphics_text_TextShaper)},
+ {"android.graphics.text.GraphemeBreak",
+ REG_JNI(register_android_graphics_text_GraphemeBreak)},
{"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
};
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index c509ed4dfd21..09ae7e78fe23 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -77,6 +77,7 @@ extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_graphics_text_TextShaper(JNIEnv *env);
+extern int register_android_graphics_text_GraphemeBreak(JNIEnv* env);
extern int register_android_graphics_MeshSpecification(JNIEnv* env);
extern int register_android_graphics_Mesh(JNIEnv* env);
@@ -148,6 +149,7 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
REG_JNI(register_android_graphics_text_MeasuredText),
REG_JNI(register_android_graphics_text_LineBreaker),
REG_JNI(register_android_graphics_text_TextShaper),
+ REG_JNI(register_android_graphics_text_GraphemeBreak),
REG_JNI(register_android_graphics_MeshSpecification),
REG_JNI(register_android_graphics_Mesh),
diff --git a/libs/hwui/jni/BufferUtils.cpp b/libs/hwui/jni/BufferUtils.cpp
new file mode 100644
index 000000000000..3eb08d7552da
--- /dev/null
+++ b/libs/hwui/jni/BufferUtils.cpp
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+#include "BufferUtils.h"
+
+#include "graphics_jni_helpers.h"
+
+static void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
+ if (src) {
+ dst.resize(srcSize);
+ memcpy(dst.data(), src, srcSize);
+ }
+}
+
+/**
+ * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
+ * from a java.nio.Buffer.
+ */
+static void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ if (pointer == 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Must use a native order direct Buffer");
+ return nullptr;
+ }
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+}
+
+static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
+ env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
+}
+
+static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining, jint* offset) {
+ jint position;
+ jint limit;
+ jint elementSizeShift;
+
+ jlong pointer;
+ pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
+ *remaining = (limit - position) << elementSizeShift;
+ if (pointer != 0L) {
+ *array = nullptr;
+ pointer += position << elementSizeShift;
+ return reinterpret_cast<void*>(pointer);
+ }
+
+ *array = jniGetNioBufferBaseArray(env, buffer);
+ *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
+ return nullptr;
+}
+
+/**
+ * This is a copy of
+ * static void android_glBufferData__IILjava_nio_Buffer_2I
+ * from com_google_android_gles_jni_GLImpl.cpp
+ */
+static void setIndirectData(JNIEnv* env, size_t size, jobject data_buf,
+ std::vector<uint8_t>& result) {
+ jint exception = 0;
+ const char* exceptionType = nullptr;
+ const char* exceptionMessage = nullptr;
+ jarray array = nullptr;
+ jint bufferOffset = 0;
+ jint remaining;
+ void* data = 0;
+ char* dataBase = nullptr;
+
+ if (data_buf) {
+ data = getPointer(env, data_buf, (jarray*)&array, &remaining, &bufferOffset);
+ if (remaining < size) {
+ exception = 1;
+ exceptionType = "java/lang/IllegalArgumentException";
+ exceptionMessage = "remaining() < size < needed";
+ goto exit;
+ }
+ }
+ if (data_buf && data == nullptr) {
+ dataBase = (char*)env->GetPrimitiveArrayCritical(array, (jboolean*)0);
+ data = (void*)(dataBase + bufferOffset);
+ }
+
+ copyToVector(result, data, size);
+
+exit:
+ if (array) {
+ releasePointer(env, array, (void*)dataBase, JNI_FALSE);
+ }
+ if (exception) {
+ jniThrowException(env, exceptionType, exceptionMessage);
+ }
+}
+
+std::vector<uint8_t> copyJavaNioBufferToVector(JNIEnv* env, jobject buffer, size_t size,
+ jboolean isDirect) {
+ std::vector<uint8_t> data;
+ if (buffer == nullptr) {
+ jniThrowNullPointerException(env);
+ } else {
+ if (isDirect) {
+ void* directBufferPtr = getDirectBufferPointer(env, buffer);
+ if (directBufferPtr) {
+ copyToVector(data, directBufferPtr, size);
+ }
+ } else {
+ setIndirectData(env, size, buffer, data);
+ }
+ }
+ return data;
+}
diff --git a/libs/hwui/jni/BufferUtils.h b/libs/hwui/jni/BufferUtils.h
new file mode 100644
index 000000000000..b43c320b7771
--- /dev/null
+++ b/libs/hwui/jni/BufferUtils.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+#ifndef BUFFERUTILS_H_
+#define BUFFERUTILS_H_
+
+#include <jni.h>
+
+#include <vector>
+
+/**
+ * Helper method to load a java.nio.Buffer instance into a vector. This handles
+ * both direct and indirect buffers and promptly releases any critical arrays that
+ * have been retrieved in order to avoid potential jni exceptions due to interleaved
+ * jni calls between get/release primitive method invocations.
+ */
+std::vector<uint8_t> copyJavaNioBufferToVector(JNIEnv* env, jobject buffer, size_t size,
+ jboolean isDirect);
+
+#endif // BUFFERUTILS_H_
diff --git a/libs/hwui/jni/android_graphics_Mesh.cpp b/libs/hwui/jni/android_graphics_Mesh.cpp
index 04339dc8b9a5..5cb43e54e499 100644
--- a/libs/hwui/jni/android_graphics_Mesh.cpp
+++ b/libs/hwui/jni/android_graphics_Mesh.cpp
@@ -14,172 +14,18 @@
* limitations under the License.
*/
-#include <GrDirectContext.h>
#include <Mesh.h>
#include <SkMesh.h>
#include <jni.h>
-#include <log/log.h>
#include <utility>
+#include "BufferUtils.h"
#include "GraphicsJNI.h"
#include "graphics_jni_helpers.h"
#define gIndexByteSize 2
-// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
-// direct and indrect buffers, allowing access to the underlying data in both
-// situations. If passed a null buffer, we will throw NullPointerException,
-// and c_data will return nullptr.
-//
-// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
-// conversion.
-class ScopedJavaNioBuffer {
-public:
- ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, size_t size, jboolean isDirect)
- : mEnv(env), mBuffer(buffer) {
- if (buffer == nullptr) {
- mDataBase = nullptr;
- mData = nullptr;
- jniThrowNullPointerException(env);
- } else {
- mArray = (jarray) nullptr;
- if (isDirect) {
- mData = getDirectBufferPointer(mEnv, mBuffer);
- } else {
- mData = setIndirectData(size);
- }
- }
- }
-
- ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }
-
- ~ScopedJavaNioBuffer() { reset(); }
-
- void reset() {
- if (mDataBase) {
- releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
- mDataBase = nullptr;
- }
- }
-
- ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
- if (this != &rhs) {
- reset();
-
- mEnv = rhs.mEnv;
- mBuffer = rhs.mBuffer;
- mDataBase = rhs.mDataBase;
- mData = rhs.mData;
- mArray = rhs.mArray;
- rhs.mEnv = nullptr;
- rhs.mData = nullptr;
- rhs.mBuffer = nullptr;
- rhs.mArray = nullptr;
- rhs.mDataBase = nullptr;
- }
- return *this;
- }
-
- const void* data() const { return mData; }
-
-private:
- /**
- * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
- * from a java.nio.Buffer.
- */
- void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
- if (buffer == nullptr) {
- return nullptr;
- }
-
- jint position;
- jint limit;
- jint elementSizeShift;
- jlong pointer;
- pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
- if (pointer == 0) {
- jniThrowException(mEnv, "java/lang/IllegalArgumentException",
- "Must use a native order direct Buffer");
- return nullptr;
- }
- pointer += position << elementSizeShift;
- return reinterpret_cast<void*>(pointer);
- }
-
- static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
- env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
- }
-
- static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
- jint* offset) {
- jint position;
- jint limit;
- jint elementSizeShift;
-
- jlong pointer;
- pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
- *remaining = (limit - position) << elementSizeShift;
- if (pointer != 0L) {
- *array = nullptr;
- pointer += position << elementSizeShift;
- return reinterpret_cast<void*>(pointer);
- }
-
- *array = jniGetNioBufferBaseArray(env, buffer);
- *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
- return nullptr;
- }
-
- /**
- * This is a copy of
- * static void android_glBufferData__IILjava_nio_Buffer_2I
- * from com_google_android_gles_jni_GLImpl.cpp
- */
- void* setIndirectData(size_t size) {
- jint exception;
- const char* exceptionType;
- const char* exceptionMessage;
- jint bufferOffset = (jint)0;
- jint remaining;
- void* tempData;
-
- if (mBuffer) {
- tempData =
- (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
- if (remaining < size) {
- exception = 1;
- exceptionType = "java/lang/IllegalArgumentException";
- exceptionMessage = "remaining() < size < needed";
- goto exit;
- }
- }
- if (mBuffer && tempData == nullptr) {
- mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
- tempData = (void*)(mDataBase + bufferOffset);
- }
- return tempData;
- exit:
- if (mArray) {
- releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
- }
- if (exception) {
- jniThrowException(mEnv, exceptionType, exceptionMessage);
- }
- return nullptr;
- }
-
- JNIEnv* mEnv;
-
- // Java Buffer data
- void* mData;
- jobject mBuffer;
-
- // Indirect Buffer Data
- jarray mArray;
- char* mDataBase;
-};
-
namespace android {
static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
@@ -187,9 +33,12 @@ static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject verte
jfloat right, jfloat bottom) {
auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
size_t bufferSize = vertexCount * skMeshSpec->stride();
- auto buff = ScopedJavaNioBuffer(env, vertexBuffer, bufferSize, isDirect);
+ auto buffer = copyJavaNioBufferToVector(env, vertexBuffer, bufferSize, isDirect);
+ if (env->ExceptionCheck()) {
+ return 0;
+ }
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, buff.data(), bufferSize, vertexCount, vertexOffset,
+ auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
@@ -205,11 +54,17 @@ static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobjec
auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
auto vertexBufferSize = vertexCount * skMeshSpec->stride();
auto indexBufferSize = indexCount * gIndexByteSize;
- auto vBuf = ScopedJavaNioBuffer(env, vertexBuffer, vertexBufferSize, isVertexDirect);
- auto iBuf = ScopedJavaNioBuffer(env, indexBuffer, indexBufferSize, isIndexDirect);
+ auto vBuf = copyJavaNioBufferToVector(env, vertexBuffer, vertexBufferSize, isVertexDirect);
+ if (env->ExceptionCheck()) {
+ return 0;
+ }
+ auto iBuf = copyJavaNioBufferToVector(env, indexBuffer, indexBufferSize, isIndexDirect);
+ if (env->ExceptionCheck()) {
+ return 0;
+ }
auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
- auto meshPtr = new Mesh(skMeshSpec, mode, vBuf.data(), vertexBufferSize, vertexCount,
- vertexOffset, iBuf.data(), indexBufferSize, indexCount, indexOffset,
+ auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
+ std::move(iBuf), indexCount, indexOffset,
std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
auto [valid, msg] = meshPtr->validate();
if (!valid) {
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f17129c8d953..1af60b2f5fae 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -108,8 +108,9 @@ static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jo
std::move(data), std::string_view(fontPath.c_str(), fontPath.size()),
fontPtr, fontSize, ttcIndex, builder->axes);
if (minikinFont == nullptr) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "Failed to create internal object. maybe invalid font data.");
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Failed to create internal object. maybe invalid font data. filePath %s",
+ fontPath.c_str());
return 0;
}
uint32_t localeListId = minikin::registerLocaleList(langTagStr.c_str());
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
new file mode 100644
index 000000000000..55f03bd9f7b1
--- /dev/null
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "GraphemeBreaker"
+
+#include <minikin/GraphemeBreak.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+
+#include "GraphicsJNI.h"
+
+namespace android {
+
+static void nIsGraphemeBreak(JNIEnv* env, jclass, jfloatArray advances, jcharArray text, jint start,
+ jint end, jbooleanArray isGraphemeBreak) {
+ if (start > end || env->GetArrayLength(advances) < end ||
+ env->GetArrayLength(isGraphemeBreak) < end - start) {
+ doThrowAIOOBE(env);
+ }
+
+ if (start == end) {
+ return;
+ }
+
+ ScopedFloatArrayRO advancesArray(env, advances);
+ ScopedCharArrayRO textArray(env, text);
+ ScopedBooleanArrayRW isGraphemeBreakArray(env, isGraphemeBreak);
+
+ size_t count = end - start;
+ for (size_t offset = 0; offset < count; ++offset) {
+ bool isBreak = minikin::GraphemeBreak::isGraphemeBreak(advancesArray.get(), textArray.get(),
+ start, end, start + offset);
+ isGraphemeBreakArray[offset] = isBreak ? JNI_TRUE : JNI_FALSE;
+ }
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"nIsGraphemeBreak",
+ "("
+ "[F" // advances
+ "[C" // text
+ "I" // start
+ "I" // end
+ "[Z" // isGraphemeBreak
+ ")V",
+ (void*)nIsGraphemeBreak},
+};
+
+int register_android_graphics_text_GraphemeBreak(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/graphics/text/GraphemeBreak", gMethods,
+ NELEM(gMethods));
+}
+
+} // namespace android
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index f85bdee18967..5f5e214357ea 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -252,10 +252,10 @@ public class AudioMix {
if (o == null || getClass() != o.getClass()) return false;
final AudioMix that = (AudioMix) o;
- return (this.mRouteFlags == that.mRouteFlags)
- && (this.mRule == that.mRule)
- && (this.mMixType == that.mMixType)
- && (this.mFormat == that.mFormat);
+ return (mRouteFlags == that.mRouteFlags)
+ && (mMixType == that.mMixType)
+ && Objects.equals(mRule, that.mRule)
+ && Objects.equals(mFormat, that.mFormat);
}
/** @hide */
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 440447e5ec1d..ce9773312a10 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -24,6 +24,7 @@ import android.os.Parcelable;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Objects;
@@ -50,7 +51,8 @@ public class AudioPolicyConfig implements Parcelable {
mMixes = conf.mMixes;
}
- AudioPolicyConfig(ArrayList<AudioMix> mixes) {
+ @VisibleForTesting
+ public AudioPolicyConfig(ArrayList<AudioMix> mixes) {
mMixes = mixes;
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 9e9012e43111..178a6d97dff8 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -191,20 +191,13 @@ public final class MediaProjection {
} else {
session = ContentRecordingSession.createTaskSession(launchCookie);
}
- virtualDisplayConfig.setWindowManagerMirroring(true);
+ // Pass in the current session details, so they are guaranteed to only be set in WMS
+ // AFTER a VirtualDisplay is constructed (assuming there are no errors during set-up).
+ virtualDisplayConfig.setContentRecordingSession(session);
+ virtualDisplayConfig.setWindowManagerMirroringEnabled(true);
final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this,
virtualDisplayConfig.build(), callback, handler, windowContext);
- if (virtualDisplay == null) {
- // Since WM handling a new display and DM creating a new VirtualDisplay is async,
- // WM may have tried to start task recording and encountered an error that required
- // stopping recording entirely. The VirtualDisplay would then be null when the
- // MediaProjection is no longer active.
- return null;
- }
- session.setDisplayId(virtualDisplay.getDisplay().getDisplayId());
- // Successfully set up, so save the current session details.
- getProjectionService().setContentRecordingSession(session, mImpl);
return virtualDisplay;
} catch (RemoteException e) {
// Can not capture if WMS is not accessible, so bail out.
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index 96532d00831b..2273f816ac60 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -1,3 +1,4 @@
michaelwr@google.com
santoscordon@google.com
chaviw@google.com
+nmusgrave@google.com
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index e9aa321018f3..113c85802fa2 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -118,6 +118,9 @@ interface ITvInputManager {
void requestAd(in IBinder sessionToken, in AdRequest request, int userId);
void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId);
+ // For TV Message
+ void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId);
+
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 82875e5d72f1..165a9ddc26e9 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -76,4 +76,7 @@ oneway interface ITvInputSession {
// For ad request
void requestAd(in AdRequest request);
void notifyAdBuffer(in AdBuffer buffer);
+
+ // For TV messages
+ void notifyTvMessage(in String type, in Bundle data);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 465b617db1f4..8389706b501c 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -78,6 +78,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
private static final int DO_SELECT_AUDIO_PRESENTATION = 29;
private static final int DO_TIME_SHIFT_SET_MODE = 30;
private static final int DO_SET_TV_MESSAGE_ENABLED = 31;
+ private static final int DO_NOTIFY_TV_MESSAGE = 32;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -277,6 +278,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj);
break;
}
+ case DO_NOTIFY_TV_MESSAGE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.onTvMessageReceived((String) args.arg1, (Bundle) args.arg2);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -463,6 +469,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer));
}
+ @Override
+ public void notifyTvMessage(String type, Bundle data) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
+ }
+
private final class TvInputEventReceiver extends InputEventReceiver {
TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 8459538f102d..55a753f663c8 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -144,6 +144,13 @@ public final class TvInputManager {
@StringDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_CLOSED_CAPTION})
public @interface TvMessageType {}
+ /**
+ * This constant is used as a {@link Bundle} key for TV messages. The value of the key
+ * identifies the stream on the TV input source for which the watermark event is relevant to.
+ */
+ public static final String TV_MESSAGE_KEY_STREAM_ID =
+ "android.media.tv.TvInputManager.stream_id";
+
static final int VIDEO_UNAVAILABLE_REASON_START = 0;
static final int VIDEO_UNAVAILABLE_REASON_END = 18;
@@ -3221,6 +3228,17 @@ public final class TvInputManager {
}
/**
+ * Sends TV messages to the service for testing purposes
+ */
+ public void notifyTvMessage(@NonNull @TvMessageType String type, @NonNull Bundle data) {
+ try {
+ mService.notifyTvMessage(mToken, type, data, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts TV program recording in the current recording session.
*
* @param programUri The URI for the TV program to record as a hint, built by
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4bc137da6f00..9f40d704f7ac 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1491,7 +1491,18 @@ public abstract class TvInputService extends Service {
* {@code false} otherwise.
*/
public void onSetTvMessageEnabled(@NonNull @TvInputManager.TvMessageType String type,
- boolean enabled){
+ boolean enabled) {
+ }
+
+ /**
+ * Called when a TV message is received
+ *
+ * @param type The type of message received, such as
+ * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
+ * @param data The raw data of the message
+ */
+ public void onTvMessage(@NonNull @TvInputManager.TvMessageType String type,
+ @NonNull Bundle data) {
}
/**
@@ -2043,6 +2054,10 @@ public abstract class TvInputService extends Service {
onAdBuffer(buffer);
}
+ void onTvMessageReceived(String type, Bundle data) {
+ onTvMessage(type, data);
+ }
+
/**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 3ef61f289e23..5aeed1fbbaf5 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -640,6 +641,20 @@ public class TvView extends ViewGroup {
}
}
+
+ /**
+ * Sends TV messages to the session for testing purposes
+ *
+ * @hide
+ */
+ @TestApi
+ public void notifyTvMessage(@TvInputManager.TvMessageType @NonNull String type,
+ @NonNull Bundle data) {
+ if (mSession != null) {
+ mSession.notifyTvMessage(type, data);
+ }
+ }
+
/**
* Sets the callback to be invoked when the time shift position is changed.
*
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index 7e9443b467ef..c39a6dbd17da 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -32,6 +32,7 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.NullPointerException;
import java.util.concurrent.Executor;
/**
@@ -271,7 +272,12 @@ public class Filter implements AutoCloseable {
mExecutor.execute(() -> {
synchronized (mCallbackLock) {
if (mCallback != null) {
- mCallback.onFilterStatusChanged(this, status);
+ try {
+ mCallback.onFilterStatusChanged(this, status);
+ }
+ catch (NullPointerException e) {
+ Log.d(TAG, "catch exception:" + e);
+ }
}
}
});
@@ -285,7 +291,12 @@ public class Filter implements AutoCloseable {
mExecutor.execute(() -> {
synchronized (mCallbackLock) {
if (mCallback != null) {
- mCallback.onFilterEvent(this, events);
+ try {
+ mCallback.onFilterEvent(this, events);
+ }
+ catch (NullPointerException e) {
+ Log.d(TAG, "catch exception:" + e);
+ }
} else {
for (FilterEvent event : events) {
if (event instanceof MediaEvent) {
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 63292ce766f8..4624dfe70756 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -14,6 +14,7 @@ android_test {
"androidx.test.ext.junit",
"androidx.test.rules",
"guava",
+ "guava-android-testlib",
"hamcrest-library",
"platform-test-annotations",
],
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
new file mode 100644
index 000000000000..bbca8823dde4
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
@@ -0,0 +1,158 @@
+/*
+ * 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.audiopolicytest;
+
+import static android.media.AudioFormat.CHANNEL_OUT_MONO;
+import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
+import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
+import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
+
+import android.media.AudioFormat;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicyConfig;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for AudioMix.
+ *
+ * Run with "atest AudioMixUnitTests".
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioMixUnitTests {
+ private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(44000)
+ .setChannelMask(CHANNEL_OUT_STEREO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+ private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setChannelMask(CHANNEL_OUT_MONO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+ private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM =
+ new AudioFormat.Builder()
+ .setSampleRate(16000)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .setEncoding(ENCODING_PCM_16BIT).build();
+
+ @Test
+ public void testEquals() {
+ final EqualsTester equalsTester = new EqualsTester();
+
+ // --- Equality group 1
+ final AudioMix playbackAudioMixWithSessionId42AndUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ final AudioMix playbackAudioMixWithUid123AndSessionId42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 123)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build())
+ .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(
+ playbackAudioMixWithSessionId42AndUid123,
+ playbackAudioMixWithUid123AndSessionId42,
+ writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123),
+ writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42));
+
+ // --- Equality group 2
+ final AudioMix recordingAudioMixWithSessionId42AndUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ final AudioMix recordingAudioMixWithUid123AndSessionId42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123,
+ recordingAudioMixWithUid123AndSessionId42,
+ writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123),
+ writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42));
+
+ // --- Equality group 3
+ final AudioMix recordingAudioMixWithSessionId42AndUid123Render =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_INJECTOR)
+ .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(
+ AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build();
+ equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render,
+ writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render));
+
+ // --- Equality group 4
+ final AudioMix playbackAudioMixWithUid123 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 123).build())
+ .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(playbackAudioMixWithUid123,
+ writeToAndFromParcel(playbackAudioMixWithUid123));
+
+ // --- Equality group 5
+ final AudioMix playbackAudioMixWithUid42 =
+ new AudioMix.Builder(new AudioMixingRule.Builder()
+ .setTargetMixRole(MIX_ROLE_PLAYERS)
+ .addMixRule(RULE_MATCH_UID, 42).build())
+ .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
+ equalsTester.addEqualityGroup(playbackAudioMixWithUid42,
+ writeToAndFromParcel(playbackAudioMixWithUid42));
+
+ equalsTester.testEquals();
+ }
+
+ private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
+ AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix)));
+ Parcel parcel = Parcel.obtain();
+ apc.writeToParcel(parcel, /*flags=*/0);
+ parcel.setDataPosition(0);
+ AudioMix unmarshalledMix =
+ AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0);
+ parcel.recycle();
+ return unmarshalledMix;
+ }
+}
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index b8427613d8ce..82e5a7f0682a 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -36,17 +36,19 @@
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
<string name="summary_watch_single_device">The app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, and access these permissions:</string>
- <!-- TODO(b/256140614) To replace all glasses related strings with final versions -->
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
+ <!-- Title of the device association confirmation dialog for glasses. -->
+ <string name="confirmation_title_glasses">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to manage &lt;strong&gt;<xliff:g id="device_name" example="Glasses">%2$s</xliff:g>&lt;/strong&gt;?</string>
+
<!-- The name of the "glasses" device type [CHAR LIMIT=30] -->
<string name="profile_name_glasses">glasses</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile (type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+ <string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses_single_device">The app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with these permissions:</string>
+ <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your phone:</string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -81,17 +83,13 @@
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
- <!-- TODO(b/256140614) To replace all nearby_device_streaming related strings with final versions -->
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
<!-- Confirmation for associating an application with a companion device of NEARBY_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to perform this action from your phone</string>
-
- <!-- Title of the helper dialog for NEARBY_DEVICE_STREAMING profile [CHAR LIMIT=30]. -->
- <string name="helper_title_nearby_device_streaming">Cross-device services</string>
+ <string name="title_nearby_device_streaming">Allow &lt;strong&gt;<xliff:g id="device_name" example="NearbyStreamer">%1$s</xliff:g>&lt;/strong&gt; to take this action?</string>
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="NearbyDevice">%2$s</xliff:g> to stream content to nearby devices</string>
+ <string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="NearbyStreamerApp">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_name" example="NearbyDevice">%2$s</xliff:g> to stream apps and other system features to nearby devices</string>
<!-- ================= null profile ================= -->
@@ -161,7 +159,7 @@
<string name="permission_app_streaming">Apps</string>
<!-- Nearby_device_streaming permission will be granted to the corresponding profile [CHAR LIMIT=45] -->
- <string name="permission_nearby_device_streaming">Nearby Device Streaming</string>
+ <string name="permission_nearby_device_streaming">Streaming</string>
<!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_phone_summary">Can make and manage phone calls</string>
@@ -179,8 +177,7 @@
<string name="permission_calendar_summary">Can access your calendar</string>
<!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
- <!-- TODO(b/256140614) Need the description for microphone permission -->
- <string name="permission_microphone_summary">Can record audio using the microphone</string>
+ <string name="permission_microphone_summary">Can record audio</string>
<!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
<string name="permission_nearby_devices_summary">Can find, connect to, and determine the relative position of nearby devices</string>
@@ -195,7 +192,6 @@
<string name="permission_storage_summary"></string>
<!-- Description of nearby_device_streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
- <!-- TODO(b/256140614) Need the description for nearby devices' permission -->
- <string name="permission_nearby_device_streaming_summary">Stream content to a nearby device</string>
+ <string name="permission_nearby_device_streaming_summary">Stream apps and other system features from your phone</string>
</resources>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 918f9c681c5a..8316f9df323f 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -30,6 +30,7 @@ import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService
import static com.android.companiondevicemanager.CompanionDeviceResources.MULTI_DEVICES_SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME_MULTI;
import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
@@ -571,6 +572,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements
final String deviceProfile = mRequest.getDeviceProfile();
final String profileName;
+ final String profileNameMulti;
final Spanned summary;
final Drawable profileIcon;
final int summaryResourceId;
@@ -580,6 +582,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements
}
profileName = getString(PROFILES_NAME.get(deviceProfile));
+ profileNameMulti = getString(PROFILES_NAME_MULTI.get(deviceProfile));
profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
summaryResourceId = MULTI_DEVICES_SUMMARIES.get(deviceProfile);
@@ -590,7 +593,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements
}
final Spanned title = getHtmlFromResources(
- this, R.string.chooser_title, profileName, appLabel);
+ this, R.string.chooser_title, profileNameMulti, appLabel);
mTitle.setText(title);
mSummary.setText(summary);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index e3fd3545ca4b..7aed13960b08 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -59,7 +59,7 @@ final class CompanionDeviceResources {
map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
- map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title);
+ map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
map.put(null, R.string.confirmation_title);
TITLES = unmodifiableMap(map);
@@ -97,7 +97,7 @@ final class CompanionDeviceResources {
static {
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
- map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
+ map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses_multi_device);
map.put(null, R.string.summary_generic);
MULTI_DEVICES_SUMMARIES = unmodifiableMap(map);
@@ -113,6 +113,16 @@ final class CompanionDeviceResources {
PROFILES_NAME = unmodifiableMap(map);
}
+ static final Map<String, Integer> PROFILES_NAME_MULTI;
+ static {
+ final Map<String, Integer> map = new ArrayMap<>();
+ map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_generic);
+ map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
+ map.put(null, R.string.profile_name_generic);
+
+ PROFILES_NAME_MULTI = unmodifiableMap(map);
+ }
+
static final Map<String, Integer> PROFILE_ICON;
static {
final Map<String, Integer> map = new ArrayMap<>();
@@ -133,7 +143,6 @@ final class CompanionDeviceResources {
SUPPORTED_PROFILES = unmodifiableSet(set);
}
-
static final Set<String> SUPPORTED_SELF_MANAGED_PROFILES;
static {
final Set<String> set = new ArraySet<>();
@@ -145,6 +154,4 @@ final class CompanionDeviceResources {
SUPPORTED_SELF_MANAGED_PROFILES = unmodifiableSet(set);
}
-
-
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
index eae14a6ac175..8f32dbb86d04 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionVendorHelperDialogFragment.java
@@ -21,6 +21,7 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
import static com.android.companiondevicemanager.Utils.getApplicationIcon;
+import static com.android.companiondevicemanager.Utils.getApplicationLabel;
import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
import android.annotation.Nullable;
@@ -105,9 +106,11 @@ public class CompanionVendorHelperDialogFragment extends DialogFragment {
final String packageName = request.getPackageName();
final CharSequence displayName = request.getDisplayName();
final int userId = request.getUserId();
+ final CharSequence appLabel;
try {
applicationIcon = getApplicationIcon(getContext(), packageName);
+ appLabel = getApplicationLabel(getContext(), packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Package u" + userId + "/" + packageName + " not found.");
mListener.onShowHelperDialogFailed();
@@ -119,7 +122,7 @@ public class CompanionVendorHelperDialogFragment extends DialogFragment {
mAppIcon = view.findViewById(R.id.app_icon);
mButton = view.findViewById(R.id.btn_back);
- final Spanned title;
+ final CharSequence title;
final Spanned summary;
switch (deviceProfile) {
@@ -136,8 +139,7 @@ public class CompanionVendorHelperDialogFragment extends DialogFragment {
break;
case DEVICE_PROFILE_NEARBY_DEVICE_STREAMING:
- title = getHtmlFromResources(getContext(),
- R.string.helper_title_nearby_device_streaming);
+ title = appLabel;
summary = getHtmlFromResources(
getContext(), R.string.helper_summary_nearby_device_streaming, title,
displayName);
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index ee512427fa9c..f655d6b174d5 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -42,7 +42,7 @@
<!-- Title for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=80] -->
<string name="seamless_transition_title">Seamless transition</string>
<!-- Detail for subsection of "Learn more about passkeys" screen about seamless transition. [CHAR LIMIT=500] -->
- <string name="seamless_transition_detail">As we move towards a passwordless future, passwords will still be available alongside passkeys.</string>
+ <string name="seamless_transition_detail">As we move towards a passwordless future, passwords will still be available alongside passkeys</string>
<!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
<string name="choose_provider_title">Choose where to save your <xliff:g id="createTypes" example="passkeys">%1$s</xliff:g></string>
<!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
@@ -67,9 +67,8 @@
<string name="create_passkey_in_other_device_title">Create passkey in another device?</string>
<!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
<string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
- <!-- TODO: Check the wording here. -->
- <!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
- <string name="use_provider_for_all_description">This password manager will store your passwords and passkeys to help you easily sign in</string>
+ <!-- This appears as the description body of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=300] -->
+ <string name="use_provider_for_all_description">This password manager for <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g> will store your passwords and passkeys to help you easily sign in</string>
<!-- This is a label for a button that sets this password manager as the default. [CHAR LIMIT=20] -->
<string name="set_as_default">Set as default</string>
<!-- This is a label for a button that makes this password manager be used just in this specific case. [CHAR LIMIT=20] -->
@@ -111,7 +110,7 @@
<!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] -->
<string name="get_dialog_button_label_continue">Continue</string>
<!-- Separator for sign-in type and username in a sign-in entry. -->
- <string name="get_dialog_sign_in_type_username_separator" translatable="false">" - "</string>
+ <string name="get_dialog_sign_in_type_username_separator" translatable="false">" • "</string>
<!-- This text is followed by a list of one or more options. [CHAR LIMIT=80] -->
<string name="get_dialog_title_sign_in_options">Sign-in options</string>
<!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/res/values/themes.xml b/packages/CredentialManager/res/values/themes.xml
index 82aebb7a179d..428c85ac721f 100644
--- a/packages/CredentialManager/res/values/themes.xml
+++ b/packages/CredentialManager/res/values/themes.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="Theme.CredentialSelector" parent="@android:style/ThemeOverlay.Material">
+ <style name="Theme.CredentialSelector" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent.DayNight">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index a2d1e4de8e35..b32fe3fef00d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -42,11 +42,13 @@ import com.android.credentialmanager.createflow.DisabledProviderInfo
import com.android.credentialmanager.createflow.EnabledProviderInfo
import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
+import com.android.credentialmanager.getflow.findAutoSelectEntry
import androidx.credentials.CreateCredentialRequest.DisplayInfo
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.CreatePasswordRequest
import androidx.credentials.GetPasswordOption
import androidx.credentials.GetPublicKeyCredentialOption
+import com.android.credentialmanager.common.ProviderActivityState
import java.time.Instant
@@ -128,10 +130,20 @@ class CredentialManagerRepo(
getCredentialUiState = null,
)
}
- RequestInfo.TYPE_GET -> UiState(
- createCredentialUiState = null,
- getCredentialUiState = getCredentialInitialUiState(originName)!!,
- )
+ RequestInfo.TYPE_GET -> {
+ val getCredentialInitialUiState = getCredentialInitialUiState(originName)!!
+ val autoSelectEntry =
+ findAutoSelectEntry(getCredentialInitialUiState.providerDisplayInfo)
+ UiState(
+ createCredentialUiState = null,
+ getCredentialUiState = getCredentialInitialUiState,
+ selectedEntry = autoSelectEntry,
+ providerActivityState =
+ if (autoSelectEntry == null) ProviderActivityState.NOT_APPLICABLE
+ else ProviderActivityState.READY_TO_LAUNCH,
+ isAutoSelectFlow = autoSelectEntry != null,
+ )
+ }
else -> throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 9b7139ccc26e..e7de8b3f128c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -22,6 +22,7 @@ import android.util.Log
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
@@ -36,6 +37,8 @@ import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
+import com.android.credentialmanager.logging.UIMetrics
+import com.android.internal.logging.UiEventLogger.UiEventEnum
/** One and only one of create or get state can be active at any given time. */
data class UiState(
@@ -44,6 +47,9 @@ data class UiState(
val selectedEntry: BaseEntry? = null,
val providerActivityState: ProviderActivityState = ProviderActivityState.NOT_APPLICABLE,
val dialogState: DialogState = DialogState.ACTIVE,
+ // True if the UI has one and only one auto selectable entry. Its provider activity will be
+ // launched immediately, and canceling it will cancel the whole UI flow.
+ val isAutoSelectFlow: Boolean = false,
)
class CredentialSelectorViewModel(
@@ -53,6 +59,8 @@ class CredentialSelectorViewModel(
var uiState by mutableStateOf(credManRepo.initState())
private set
+ var uiMetrics: UIMetrics = UIMetrics()
+
/**************************************************************************/
/***** Shared Callbacks *****/
/**************************************************************************/
@@ -73,6 +81,10 @@ class CredentialSelectorViewModel(
fun onNewCredentialManagerRepo(credManRepo: CredentialManagerRepo) {
this.credManRepo = credManRepo
uiState = credManRepo.initState()
+
+ if (this.credManRepo.requestInfo.token != credManRepo.requestInfo.token) {
+ this.uiMetrics.resetInstanceId()
+ }
}
fun launchProviderUi(
@@ -96,13 +108,20 @@ class CredentialSelectorViewModel(
val resultCode = providerActivityResult.resultCode
val resultData = providerActivityResult.data
if (resultCode == Activity.RESULT_CANCELED) {
- // Re-display the CredMan UI if the user canceled from the provider UI.
- Log.d(Constants.LOG_TAG, "The provider activity was cancelled," +
- " re-displaying our UI.")
- uiState = uiState.copy(
- selectedEntry = null,
- providerActivityState = ProviderActivityState.NOT_APPLICABLE,
- )
+ // Re-display the CredMan UI if the user canceled from the provider UI, or cancel
+ // the UI if this is the auto select flow.
+ if (uiState.isAutoSelectFlow) {
+ Log.d(Constants.LOG_TAG, "The auto selected provider activity was cancelled," +
+ " ending the credential manager activity.")
+ onUserCancel()
+ } else {
+ Log.d(Constants.LOG_TAG, "The provider activity was cancelled," +
+ " re-displaying our UI.")
+ uiState = uiState.copy(
+ selectedEntry = null,
+ providerActivityState = ProviderActivityState.NOT_APPLICABLE,
+ )
+ }
} else {
if (entry != null) {
Log.d(
@@ -130,6 +149,11 @@ class CredentialSelectorViewModel(
onInternalError()
}
+ fun onIllegalUiState(errorMessage: String) {
+ Log.w(Constants.LOG_TAG, errorMessage)
+ onInternalError()
+ }
+
private fun onInternalError() {
Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state")
credManRepo.onParsingFailureCancel()
@@ -359,4 +383,9 @@ class CredentialSelectorViewModel(
onInternalError()
}
}
+
+ @Composable
+ fun logUiEvent(uiEventEnum: UiEventEnum) {
+ this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo.appPackageName)
+ }
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 43912c26f3c8..b5c8989de618 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -60,8 +60,7 @@ import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
-import androidx.credentials.provider.RemoteCreateEntry
-import androidx.credentials.provider.RemoteCredentialEntry
+import androidx.credentials.provider.RemoteEntry
import org.json.JSONObject
// TODO: remove all !! checks
@@ -149,7 +148,10 @@ class GetFlowUtils {
icon = providerIcon,
displayName = providerLabel,
credentialEntryList = getCredentialOptionInfoList(
- it.providerFlattenedComponentName, it.credentialEntries, context
+ providerId = it.providerFlattenedComponentName,
+ providerLabel = providerLabel,
+ credentialEntries = it.credentialEntries,
+ context = context
),
authenticationEntryList = getAuthenticationEntryList(
it.providerFlattenedComponentName,
@@ -202,6 +204,7 @@ class GetFlowUtils {
*/
private fun getCredentialOptionInfoList(
providerId: String,
+ providerLabel: String,
credentialEntries: List<Entry>,
context: Context,
): List<CredentialEntryInfo> {
@@ -212,6 +215,7 @@ class GetFlowUtils {
is PasswordCredentialEntry -> {
result.add(CredentialEntryInfo(
providerId = providerId,
+ providerDisplayName = providerLabel,
entryKey = it.key,
entrySubkey = it.subkey,
pendingIntent = credentialEntry.pendingIntent,
@@ -221,12 +225,16 @@ class GetFlowUtils {
userName = credentialEntry.username.toString(),
displayName = credentialEntry.displayName?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
+ shouldTintIcon = credentialEntry.isDefaultIcon ?: false,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
+ isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
+ credentialEntry.autoSelectAllowedFromOption,
))
}
is PublicKeyCredentialEntry -> {
result.add(CredentialEntryInfo(
providerId = providerId,
+ providerDisplayName = providerLabel,
entryKey = it.key,
entrySubkey = it.subkey,
pendingIntent = credentialEntry.pendingIntent,
@@ -236,12 +244,16 @@ class GetFlowUtils {
userName = credentialEntry.username.toString(),
displayName = credentialEntry.displayName?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
+ shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
+ isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
+ credentialEntry.autoSelectAllowedFromOption,
))
}
is CustomCredentialEntry -> {
result.add(CredentialEntryInfo(
providerId = providerId,
+ providerDisplayName = providerLabel,
entryKey = it.key,
entrySubkey = it.subkey,
pendingIntent = credentialEntry.pendingIntent,
@@ -251,7 +263,10 @@ class GetFlowUtils {
userName = credentialEntry.title.toString(),
displayName = credentialEntry.subtitle?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
+ shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
+ isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
+ credentialEntry.autoSelectAllowedFromOption,
))
}
else -> Log.d(
@@ -320,7 +335,7 @@ class GetFlowUtils {
if (remoteEntry == null) {
return null
}
- val structuredRemoteEntry = RemoteCredentialEntry.fromSlice(remoteEntry.slice)
+ val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
?: return null
return RemoteEntryInfo(
providerId = providerId,
@@ -612,7 +627,7 @@ class CreateFlowUtils {
remoteEntry: Entry?,
): RemoteInfo? {
return if (remoteEntry != null) {
- val structuredRemoteEntry = RemoteCreateEntry.fromSlice(remoteEntry.slice)
+ val structuredRemoteEntry = RemoteEntry.fromSlice(remoteEntry.slice)
?: return null
RemoteInfo(
providerId = providerId,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
index 75b12ff756ad..26aadd9d5dee 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/TestUtils.kt
@@ -32,8 +32,7 @@ import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
import androidx.credentials.provider.CreateEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
-import androidx.credentials.provider.RemoteCreateEntry
-import androidx.credentials.provider.RemoteCredentialEntry
+import androidx.credentials.provider.RemoteEntry
import java.time.Instant
@@ -85,9 +84,7 @@ class GetTestUtils {
return Entry(
key,
subkey,
- RemoteCredentialEntry(pendingIntent, BeginGetPublicKeyCredentialOption(
- Bundle(), "id", "requestjson"
- )).slice
+ RemoteEntry(pendingIntent).slice
)
}
@@ -155,21 +152,23 @@ class GetTestUtils {
userName: String,
userDisplayName: String?,
lastUsedTime: Instant?,
+ isAutoSelectAllowed: Boolean = false,
): Entry {
- val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
- .setPackage("com.androidauth.androidvault")
- intent.putExtra("provider_extra_sample", "testprovider")
- val pendingIntent = PendingIntent.getActivity(
- context, 1,
- intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- or PendingIntent.FLAG_ONE_SHOT)
+ val intent = Intent(Settings.ACTION_SYNC_SETTINGS)
+ val pendingIntent =
+ PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
+ val candidateQueryData = Bundle()
+ candidateQueryData.putBoolean(
+ "androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED",
+ isAutoSelectAllowed
)
val passkeyEntry = PublicKeyCredentialEntry.Builder(
context,
userName,
pendingIntent,
- BeginGetPublicKeyCredentialOption(Bundle(), "id", "requestjson")
- ).setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime).build()
+ BeginGetPublicKeyCredentialOption(candidateQueryData, "id", "requestjson")
+ ).setDisplayName(userDisplayName).setLastUsedTime(lastUsedTime)
+ .setAutoSelectAllowed(isAutoSelectAllowed).build()
return Entry(key, subkey, passkeyEntry.slice, Intent())
}
}
@@ -242,7 +241,7 @@ class CreateTestUtils {
return Entry(
key,
subkey,
- RemoteCreateEntry(pendingIntent).slice
+ RemoteEntry(pendingIntent).slice
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index 0cb2bb083fb8..edc902e41e9a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -18,7 +18,6 @@ package com.android.credentialmanager.common.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.ColumnScope
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
@@ -27,6 +26,7 @@ import com.android.credentialmanager.common.material.ModalBottomSheetLayout
import com.android.credentialmanager.common.material.ModalBottomSheetValue
import com.android.credentialmanager.common.material.rememberModalBottomSheetState
import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
/** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
@Composable
@@ -39,9 +39,7 @@ fun ModalBottomSheet(
skipHalfExpanded = true
)
ModalBottomSheetLayout(
- sheetBackgroundColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
- ElevationTokens.Level1
- ),
+ sheetBackgroundColor = LocalAndroidColorScheme.current.colorSurfaceBright,
modifier = Modifier.background(Color.Transparent),
sheetState = state,
sheetContent = sheetContent,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index c535268b8989..3976f9a305ab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -25,13 +25,13 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.android.credentialmanager.ui.theme.Shapes
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
/**
* Container card for the whole sheet.
@@ -50,9 +50,7 @@ fun SheetContainerCard(
modifier = modifier.fillMaxWidth().wrapContentHeight(),
border = null,
colors = CardDefaults.cardColors(
- containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
- ElevationTokens.Level1
- ),
+ containerColor = LocalAndroidColorScheme.current.colorSurfaceBright,
),
) {
if (topAppBar != null) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 1e2a280cb693..192354258fb2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -53,6 +53,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import com.android.credentialmanager.R
import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import com.android.credentialmanager.ui.theme.Shapes
@Composable
@@ -73,6 +74,7 @@ fun Entry(
passwordValue: String? = null,
/** If true, draws a trailing lock icon. */
isLockedAuthEntry: Boolean = false,
+ enforceOneLine: Boolean = false,
) {
val iconPadding = Modifier.wrapContentSize().padding(
// Horizontal padding should be 16dp, but the suggestion chip itself
@@ -92,12 +94,18 @@ fun Entry(
// has 8dp horizontal elements padding
horizontal = 8.dp, vertical = 16.dp,
),
+ // Make sure the trailing icon and text column are centered vertically.
verticalAlignment = Alignment.CenterVertically,
) {
- Column(modifier = Modifier.wrapContentSize()) {
- SmallTitleText(entryHeadlineText)
+ // Apply weight so that the trailing icon can always show.
+ Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) {
+ SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine)
if (passwordValue != null) {
- Row(modifier = Modifier.fillMaxWidth()) {
+ Row(
+ modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start,
+ ) {
val visualTransformation = remember { PasswordVisualTransformation() }
val originalPassword by remember {
mutableStateOf(passwordValue)
@@ -109,9 +117,14 @@ fun Entry(
).text.text
)
}
- BodySmallText(displayedPassword.value)
+ BodySmallText(
+ text = displayedPassword.value,
+ // Apply weight to allow visibility button to render first so that
+ // it doesn't get squeezed out by a super long password.
+ modifier = Modifier.wrapContentSize().weight(1f, fill = false),
+ )
ToggleVisibilityButton(
- modifier = Modifier.padding(start = 5.dp).size(24.dp),
+ modifier = Modifier.padding(start = 12.dp).size(24.dp),
onToggle = {
if (it) {
displayedPassword.value = originalPassword
@@ -124,14 +137,14 @@ fun Entry(
)
}
} else if (entrySecondLineText != null) {
- BodySmallText(entrySecondLineText)
+ BodySmallText(text = entrySecondLineText, enforceOneLine = enforceOneLine)
}
if (entryThirdLineText != null) {
- BodySmallText(entryThirdLineText)
+ BodySmallText(text = entryThirdLineText, enforceOneLine = enforceOneLine)
}
}
if (isLockedAuthEntry) {
- Box(modifier = Modifier.wrapContentSize()) {
+ Box(modifier = Modifier.wrapContentSize().padding(start = 16.dp)) {
Icon(
imageVector = Icons.Outlined.Lock,
// Decorative purpose only.
@@ -198,9 +211,7 @@ fun Entry(
},
border = null,
colors = SuggestionChipDefaults.suggestionChipColors(
- containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(
- ElevationTokens.Level3
- ),
+ containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh,
// TODO: remove?
labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -323,7 +334,7 @@ fun MoreOptionTopAppBar(
contentDescription = stringResource(
R.string.accessibility_back_arrow_button
),
- modifier = Modifier.size(16.dp),
+ modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
index 2f145847cbbe..a6195237d139 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SystemUiControllerUtils.kt
@@ -16,11 +16,11 @@
package com.android.credentialmanager.common.ui
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.android.compose.SystemUiController
import com.android.credentialmanager.common.material.ModalBottomSheetDefaults
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@Composable
fun setTransparentSystemBarsColor(sysUiController: SystemUiController) {
@@ -34,9 +34,7 @@ fun setBottomSheetSystemBarsColor(sysUiController: SystemUiController) {
darkIcons = false
)
sysUiController.setNavigationBarColor(
- color = MaterialTheme.colorScheme.surfaceColorAtElevation(
- ElevationTokens.Level1
- ),
+ color = LocalAndroidColorScheme.current.colorSurfaceBright,
darkIcons = false
)
} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 8af729ecdc25..22871bcbe767 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -23,6 +23,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
/**
* The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -57,12 +58,14 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) {
* Body-small typography; on-surface-variant color.
*/
@Composable
-fun BodySmallText(text: String, modifier: Modifier = Modifier) {
+fun BodySmallText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
)
}
@@ -83,12 +86,14 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) {
* Title-small typography; on-surface color.
*/
@Composable
-fun SmallTitleText(text: String, modifier: Modifier = Modifier) {
+fun SmallTitleText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleSmall,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 5b175bf07abe..524077715b10 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -1,4 +1,18 @@
-@file:OptIn(ExperimentalMaterial3Api::class)
+/*
+ * 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.credentialmanager.createflow
@@ -15,7 +29,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Divider
-import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NewReleases
@@ -54,6 +67,8 @@ import com.android.credentialmanager.common.ui.SheetContainerCard
import com.android.credentialmanager.common.ui.PasskeyBenefitRow
import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.CreateCredentialEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
fun CreateCredentialScreen(
@@ -72,72 +87,93 @@ fun CreateCredentialScreen(
ProviderActivityState.NOT_APPLICABLE -> {
when (createCredentialUiState.currentScreenState) {
CreateScreenState.PASSKEY_INTRO -> PasskeyIntroCard(
- onConfirm = viewModel::createFlowOnConfirmIntro,
- onLearnMore = viewModel::createFlowOnLearnMore,
+ onConfirm = viewModel::createFlowOnConfirmIntro,
+ onLearnMore = viewModel::createFlowOnLearnMore,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.PROVIDER_SELECTION -> ProviderSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- disabledProviderList = createCredentialUiState.disabledProviders,
- sortedCreateOptionsPairs =
- createCredentialUiState.sortedCreateOptionsPairs,
- hasRemoteEntry = createCredentialUiState.remoteEntry != null,
- onOptionSelected =
- viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
- onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
- onMoreOptionsSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ disabledProviderList = createCredentialUiState
+ .disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ hasRemoteEntry = createCredentialUiState.remoteEntry != null,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromFirstUseScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnDisabledProvidersSelected,
+ onMoreOptionsSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnProviderSelection,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- enabledProviderList = createCredentialUiState.enabledProviders,
- providerInfo = createCredentialUiState.activeEntry?.activeProvider!!,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
- createOptionInfo =
- createCredentialUiState.activeEntry.activeEntryInfo
- as CreateOptionInfo,
- onOptionSelected = viewModel::createFlowOnEntrySelected,
- onConfirm = viewModel::createFlowOnConfirmEntrySelected,
- onMoreOptionsSelected =
- viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ providerInfo = createCredentialUiState
+ .activeEntry?.activeProvider!!,
+ hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
+ createOptionInfo =
+ createCredentialUiState.activeEntry.activeEntryInfo
+ as CreateOptionInfo,
+ onOptionSelected = viewModel::createFlowOnEntrySelected,
+ onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ onMoreOptionsSelected =
+ viewModel::createFlowOnMoreOptionsSelectedOnCreationSelection,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- enabledProviderList = createCredentialUiState.enabledProviders,
- disabledProviderList = createCredentialUiState.disabledProviders,
- sortedCreateOptionsPairs =
- createCredentialUiState.sortedCreateOptionsPairs,
- hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
- isFromProviderSelection =
- createCredentialUiState.isFromProviderSelection!!,
- onBackProviderSelectionButtonSelected =
- viewModel::createFlowOnBackProviderSelectionButtonSelected,
- onBackCreationSelectionButtonSelected =
- viewModel::createFlowOnBackCreationSelectionButtonSelected,
- onOptionSelected =
- viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
- onDisabledProvidersSelected =
- viewModel::createFlowOnDisabledProvidersSelected,
- onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
- )
- CreateScreenState.MORE_OPTIONS_ROW_INTRO -> MoreOptionsRowIntroCard(
- providerInfo = createCredentialUiState.activeEntry?.activeProvider!!,
- onChangeDefaultSelected = viewModel::createFlowOnChangeDefaultSelected,
- onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ enabledProviderList = createCredentialUiState.enabledProviders,
+ disabledProviderList = createCredentialUiState
+ .disabledProviders,
+ sortedCreateOptionsPairs =
+ createCredentialUiState.sortedCreateOptionsPairs,
+ hasDefaultProvider = createCredentialUiState.hasDefaultProvider,
+ isFromProviderSelection =
+ createCredentialUiState.isFromProviderSelection!!,
+ onBackProviderSelectionButtonSelected =
+ viewModel::createFlowOnBackProviderSelectionButtonSelected,
+ onBackCreationSelectionButtonSelected =
+ viewModel::createFlowOnBackCreationSelectionButtonSelected,
+ onOptionSelected =
+ viewModel::createFlowOnEntrySelectedFromMoreOptionScreen,
+ onDisabledProvidersSelected =
+ viewModel::createFlowOnDisabledProvidersSelected,
+ onRemoteEntrySelected = viewModel::createFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
)
+ CreateScreenState.MORE_OPTIONS_ROW_INTRO -> {
+ if (createCredentialUiState.activeEntry == null) {
+ viewModel.onIllegalUiState("Expect active entry to be non-null" +
+ " upon default provider dialog.")
+ } else {
+ MoreOptionsRowIntroCard(
+ selectedEntry = createCredentialUiState.activeEntry,
+ onIllegalScreenState = viewModel::onIllegalUiState,
+ onChangeDefaultSelected =
+ viewModel::createFlowOnChangeDefaultSelected,
+ onUseOnceSelected = viewModel::createFlowOnUseOnceSelected,
+ onLog = { viewModel.logUiEvent(it) },
+ )
+ }
+ }
CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
- requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
- activeRemoteEntry =
- createCredentialUiState.activeEntry?.activeEntryInfo!!,
- onOptionSelected = viewModel::createFlowOnEntrySelected,
- onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
+ activeRemoteEntry =
+ createCredentialUiState.activeEntry?.activeEntryInfo!!,
+ onOptionSelected = viewModel::createFlowOnEntrySelected,
+ onConfirm = viewModel::createFlowOnConfirmEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
)
- CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO ->
- MoreAboutPasskeysIntroCard(
+ CreateScreenState.MORE_ABOUT_PASSKEYS_INTRO -> MoreAboutPasskeysIntroCard(
onBackPasskeyIntroButtonSelected =
viewModel::createFlowOnBackPasskeyIntroButtonSelected,
- )
+ onLog = { viewModel.logUiEvent(it) },
+ )
}
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent
+ .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE)
}
ProviderActivityState.READY_TO_LAUNCH -> {
// Launch only once per providerActivityState change so that the provider
@@ -145,9 +181,14 @@ fun CreateCredentialScreen(
LaunchedEffect(viewModel.uiState.providerActivityState) {
viewModel.launchProviderUi(providerActivityLauncher)
}
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent
+ .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
}
ProviderActivityState.PENDING -> {
// Hide our content when the provider activity is active.
+ viewModel.uiMetrics.log(
+ CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING)
}
}
},
@@ -159,6 +200,7 @@ fun CreateCredentialScreen(
fun PasskeyIntroCard(
onConfirm: () -> Unit,
onLearnMore: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item {
@@ -223,6 +265,7 @@ fun PasskeyIntroCard(
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PASSKEY_INTRO)
}
@Composable
@@ -234,6 +277,7 @@ fun ProviderSelectionCard(
onOptionSelected: (ActiveEntry) -> Unit,
onDisabledProvidersSelected: () -> Unit,
onMoreOptionsSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item { HeadlineIcon(bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()) }
@@ -297,21 +341,23 @@ fun ProviderSelectionCard(
}
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_PROVIDER_SELECTION)
}
@Composable
fun MoreOptionsSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- disabledProviderList: List<DisabledProviderInfo>?,
- sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
- hasDefaultProvider: Boolean,
- isFromProviderSelection: Boolean,
- onBackProviderSelectionButtonSelected: () -> Unit,
- onBackCreationSelectionButtonSelected: () -> Unit,
- onOptionSelected: (ActiveEntry) -> Unit,
- onDisabledProvidersSelected: () -> Unit,
- onRemoteEntrySelected: (BaseEntry) -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ disabledProviderList: List<DisabledProviderInfo>?,
+ sortedCreateOptionsPairs: List<Pair<CreateOptionInfo, EnabledProviderInfo>>,
+ hasDefaultProvider: Boolean,
+ isFromProviderSelection: Boolean,
+ onBackProviderSelectionButtonSelected: () -> Unit,
+ onBackCreationSelectionButtonSelected: () -> Unit,
+ onOptionSelected: (ActiveEntry) -> Unit,
+ onDisabledProvidersSelected: () -> Unit,
+ onRemoteEntrySelected: (BaseEntry) -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard(topAppBar = {
MoreOptionTopAppBar(
@@ -331,7 +377,7 @@ fun MoreOptionsSelectionCard(
)
}) {
item { Divider(thickness = 8.dp, color = Color.Transparent) } // Top app bar has a 8dp
- // bottom padding already
+ // bottom padding already
item {
CredentialContainerCard {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
@@ -372,27 +418,37 @@ fun MoreOptionsSelectionCard(
}
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION)
}
@Composable
fun MoreOptionsRowIntroCard(
- providerInfo: EnabledProviderInfo,
- onChangeDefaultSelected: () -> Unit,
- onUseOnceSelected: () -> Unit,
+ selectedEntry: ActiveEntry,
+ onIllegalScreenState: (String) -> Unit,
+ onChangeDefaultSelected: () -> Unit,
+ onUseOnceSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
+ val entryInfo = selectedEntry.activeEntryInfo
+ if (entryInfo !is CreateOptionInfo) {
+ onIllegalScreenState("Encountered unexpected type of entry during the default provider" +
+ " dialog: ${entryInfo::class}")
+ return
+ }
SheetContainerCard {
item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) }
item { Divider(thickness = 24.dp, color = Color.Transparent) }
item {
HeadlineText(
text = stringResource(
- R.string.use_provider_for_all_title,
- providerInfo.displayName
- )
+ R.string.use_provider_for_all_title, selectedEntry.activeProvider.displayName)
)
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
- item { BodyMediumText(text = stringResource(R.string.use_provider_for_all_description)) }
+ item {
+ BodyMediumText(text = stringResource(
+ R.string.use_provider_for_all_description, entryInfo.userProviderDisplayName))
+ }
item {
CtaButtonRow(
leftButton = {
@@ -410,18 +466,20 @@ fun MoreOptionsRowIntroCard(
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO)
}
@Composable
fun CreationSelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- enabledProviderList: List<EnabledProviderInfo>,
- providerInfo: EnabledProviderInfo,
- createOptionInfo: CreateOptionInfo,
- onOptionSelected: (BaseEntry) -> Unit,
- onConfirm: () -> Unit,
- onMoreOptionsSelected: () -> Unit,
- hasDefaultProvider: Boolean,
+ requestDisplayInfo: RequestDisplayInfo,
+ enabledProviderList: List<EnabledProviderInfo>,
+ providerInfo: EnabledProviderInfo,
+ createOptionInfo: CreateOptionInfo,
+ onOptionSelected: (BaseEntry) -> Unit,
+ onConfirm: () -> Unit,
+ onMoreOptionsSelected: () -> Unit,
+ hasDefaultProvider: Boolean,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item {
@@ -507,14 +565,16 @@ fun CreationSelectionCard(
item { BodySmallText(text = createOptionInfo.footerDescription) }
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION)
}
@Composable
fun ExternalOnlySelectionCard(
- requestDisplayInfo: RequestDisplayInfo,
- activeRemoteEntry: BaseEntry,
- onOptionSelected: (BaseEntry) -> Unit,
- onConfirm: () -> Unit,
+ requestDisplayInfo: RequestDisplayInfo,
+ activeRemoteEntry: BaseEntry,
+ onOptionSelected: (BaseEntry) -> Unit,
+ onConfirm: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard {
item { HeadlineIcon(imageVector = Icons.Outlined.QrCodeScanner) }
@@ -542,11 +602,13 @@ fun ExternalOnlySelectionCard(
)
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION)
}
@Composable
fun MoreAboutPasskeysIntroCard(
onBackPasskeyIntroButtonSelected: () -> Unit,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
SheetContainerCard(
topAppBar = {
@@ -582,6 +644,7 @@ fun MoreAboutPasskeysIntroCard(
BodyMediumText(text = stringResource(R.string.seamless_transition_detail))
}
}
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO)
}
@Composable
@@ -620,6 +683,7 @@ fun PrimaryCreateOptionRow(
// This subtitle would never be null for create password
requestDisplayInfo.subtitle ?: ""
else null,
+ enforceOneLine = true,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 192fa15714c6..4332fb34ce79 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -69,7 +69,7 @@ class CreateOptionInfo(
entrySubkey: String,
pendingIntent: PendingIntent?,
fillInIntent: Intent?,
- val userProviderDisplayName: String?,
+ val userProviderDisplayName: String,
val profileIcon: Drawable?,
val passwordCount: Int?,
val passkeyCount: Int?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 9a826f20fe03..a9f994db430a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -182,12 +182,14 @@ fun PrimarySelectionCard(
CredentialEntryRow(
credentialEntryInfo = it.sortedCredentialEntryList.first(),
onEntrySelected = onEntrySelected,
+ enforceOneLine = true,
)
}
authenticationEntryList.forEach {
AuthenticationEntryRow(
authenticationEntryInfo = it,
onEntrySelected = onEntrySelected,
+ enforceOneLine = true,
)
}
} else if (usernameForCredentialSize < 4) {
@@ -195,12 +197,14 @@ fun PrimarySelectionCard(
CredentialEntryRow(
credentialEntryInfo = it.sortedCredentialEntryList.first(),
onEntrySelected = onEntrySelected,
+ enforceOneLine = true,
)
}
authenticationEntryList.take(4 - usernameForCredentialSize).forEach {
AuthenticationEntryRow(
authenticationEntryInfo = it,
onEntrySelected = onEntrySelected,
+ enforceOneLine = true,
)
}
} else {
@@ -208,6 +212,7 @@ fun PrimarySelectionCard(
CredentialEntryRow(
credentialEntryInfo = it.sortedCredentialEntryList.first(),
onEntrySelected = onEntrySelected,
+ enforceOneLine = true,
)
}
}
@@ -290,13 +295,6 @@ fun AllSignInOptionCard(
)
}
}
- item {
- Divider(
- thickness = 1.dp,
- color = Color.LightGray,
- modifier = Modifier.padding(top = 16.dp)
- )
- }
// Manage sign-ins (action chips)
item {
ActionChips(
@@ -402,10 +400,12 @@ fun PerUserNameCredentials(
fun CredentialEntryRow(
credentialEntryInfo: CredentialEntryInfo,
onEntrySelected: (BaseEntry) -> Unit,
+ enforceOneLine: Boolean = false,
) {
Entry(
onClick = { onEntrySelected(credentialEntryInfo) },
iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
+ shouldApplyIconImageBitmapTint = credentialEntryInfo.shouldTintIcon,
// Fall back to iconPainter if iconImageBitmap isn't available
iconPainter =
if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
@@ -415,15 +415,17 @@ fun CredentialEntryRow(
credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
"••••••••••••"
} else {
- if (TextUtils.isEmpty(credentialEntryInfo.displayName))
- credentialEntryInfo.credentialTypeDisplayName
- else
- credentialEntryInfo.credentialTypeDisplayName +
- stringResource(
- R.string.get_dialog_sign_in_type_username_separator
- ) +
- credentialEntryInfo.displayName
+ val itemsToDisplay = listOf(
+ credentialEntryInfo.displayName,
+ credentialEntryInfo.credentialTypeDisplayName,
+ credentialEntryInfo.providerDisplayName
+ ).filterNot(TextUtils::isEmpty)
+ if (itemsToDisplay.isEmpty()) null
+ else itemsToDisplay.joinToString(
+ separator = stringResource(R.string.get_dialog_sign_in_type_username_separator)
+ )
},
+ enforceOneLine = enforceOneLine,
)
}
@@ -431,6 +433,7 @@ fun CredentialEntryRow(
fun AuthenticationEntryRow(
authenticationEntryInfo: AuthenticationEntryInfo,
onEntrySelected: (BaseEntry) -> Unit,
+ enforceOneLine: Boolean = false,
) {
Entry(
onClick = { onEntrySelected(authenticationEntryInfo) },
@@ -442,6 +445,7 @@ fun AuthenticationEntryRow(
else R.string.locked_credential_entry_label_subtext_tap_to_unlock
),
isLockedAuthEntry = !authenticationEntryInfo.isUnlockedAndEmpty,
+ enforceOneLine = enforceOneLine,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 9727d3f39c4a..263a632ef5ee 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -41,6 +41,23 @@ internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean {
!state.requestDisplayInfo.preferImmediatelyAvailableCredentials)
}
+internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): CredentialEntryInfo? {
+ if (providerDisplayInfo.authenticationEntryList.isNotEmpty()) {
+ return null
+ }
+ if (providerDisplayInfo.sortedUserNameToCredentialEntryList.size == 1) {
+ val entryList = providerDisplayInfo.sortedUserNameToCredentialEntryList.firstOrNull()
+ ?: return null
+ if (entryList.sortedCredentialEntryList.size == 1) {
+ val entry = entryList.sortedCredentialEntryList.firstOrNull() ?: return null
+ if (entry.isAutoSelectable) {
+ return entry
+ }
+ }
+ }
+ return null
+}
+
data class ProviderInfo(
/**
* Unique id (component name) of this provider.
@@ -77,10 +94,13 @@ class CredentialEntryInfo(
val credentialType: CredentialType,
/** Localized type value of this credential used for display purpose. */
val credentialTypeDisplayName: String,
+ val providerDisplayName: String,
val userName: String,
val displayName: String?,
val icon: Drawable?,
+ val shouldTintIcon: Boolean,
val lastUsedTimeMillis: Instant?,
+ val isAutoSelectable: Boolean,
) : BaseEntry(
providerId,
entryKey,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
new file mode 100644
index 000000000000..daa42be020ce
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.credentialmanager.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "The create credential bottomsheet became visible on the screen.")
+ CREDMAN_CREATE_CRED_BOTTOMSHEET(1318),
+
+ @UiEvent(doc = "The provider activity is launched on the screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH(1319),
+
+ @UiEvent(doc = "The provider activity is launched and we are waiting for its result. " +
+ "Contents Hidden.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_PENDING(1320),
+
+ @UiEvent(doc = "The provider activity is not active or ready launched on the screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE(1321),
+
+ @UiEvent(doc = "The passkey introduction card is visible on screen.")
+ CREDMAN_CREATE_CRED_PASSKEY_INTRO(1322),
+
+ @UiEvent(doc = "The provider selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_PROVIDER_SELECTION(1323),
+
+ @UiEvent(doc = "The creation option selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_CREATION_OPTION_SELECTION(1324),
+
+ @UiEvent(doc = "The more option selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_OPTIONS_SELECTION(1325),
+
+ @UiEvent(doc = "The more options row intro card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_OPTIONS_ROW_INTRO(1326),
+
+ @UiEvent(doc = "The external only selection card is visible on screen.")
+ CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION(1327),
+
+ @UiEvent(doc = "The more about passkeys intro card is visible on screen.")
+ CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328);
+
+ override fun getId(): Int {
+ return this.id
+ }
+} \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
new file mode 100644
index 000000000000..4351e8406790
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.credentialmanager.logging
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLoggerImpl
+
+class UIMetrics() {
+ private val INSTANCE_ID_MAX = 1 shl 20
+ private val mUiEventLogger: UiEventLogger = UiEventLoggerImpl()
+ val mInstanceIdSequence: InstanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+
+ var mInstanceId: InstanceId = mInstanceIdSequence.newInstanceId()
+
+ fun resetInstanceId() {
+ this.mInstanceId = mInstanceIdSequence.newInstanceId()
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum) {
+ val instanceId: InstanceId = mInstanceId
+ LaunchedEffect(true) {
+ mUiEventLogger.log(event, instanceId)
+ }
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum, packageName: String) {
+ val instanceId: InstanceId = mInstanceId
+ LaunchedEffect(true) {
+ mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId)
+ }
+ }
+
+ @Composable
+ fun log(event: UiEventLogger.UiEventEnum, instanceId: InstanceId, packageName: String) {
+ LaunchedEffect(true) {
+ mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId)
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
index 120e4938c322..8928e1869838 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -40,32 +40,8 @@ val LocalAndroidColorScheme =
* most of the colors in this class will be removed in favor of their M3 counterpart.
*/
class AndroidColorScheme internal constructor(context: Context) {
- val colorPrimary = getColor(context, R.attr.colorPrimary)
- val colorPrimaryDark = getColor(context, R.attr.colorPrimaryDark)
- val colorAccent = getColor(context, R.attr.colorAccent)
- val colorAccentPrimary = getColor(context, R.attr.colorAccentPrimary)
- val colorAccentSecondary = getColor(context, R.attr.colorAccentSecondary)
- val colorAccentTertiary = getColor(context, R.attr.colorAccentTertiary)
- val colorAccentPrimaryVariant = getColor(context, R.attr.colorAccentPrimaryVariant)
- val colorAccentSecondaryVariant = getColor(context, R.attr.colorAccentSecondaryVariant)
- val colorAccentTertiaryVariant = getColor(context, R.attr.colorAccentTertiaryVariant)
- val colorSurface = getColor(context, R.attr.colorSurface)
- val colorSurfaceHighlight = getColor(context, R.attr.colorSurfaceHighlight)
- val colorSurfaceVariant = getColor(context, R.attr.colorSurfaceVariant)
- val colorSurfaceHeader = getColor(context, R.attr.colorSurfaceHeader)
- val colorError = getColor(context, R.attr.colorError)
- val colorBackground = getColor(context, R.attr.colorBackground)
- val colorBackgroundFloating = getColor(context, R.attr.colorBackgroundFloating)
- val panelColorBackground = getColor(context, R.attr.panelColorBackground)
- val textColorPrimary = getColor(context, R.attr.textColorPrimary)
- val textColorSecondary = getColor(context, R.attr.textColorSecondary)
- val textColorTertiary = getColor(context, R.attr.textColorTertiary)
- val textColorPrimaryInverse = getColor(context, R.attr.textColorPrimaryInverse)
- val textColorSecondaryInverse = getColor(context, R.attr.textColorSecondaryInverse)
- val textColorTertiaryInverse = getColor(context, R.attr.textColorTertiaryInverse)
- val textColorOnAccent = getColor(context, R.attr.textColorOnAccent)
- val colorForeground = getColor(context, R.attr.colorForeground)
- val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
+ val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
+ val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
companion object {
fun getColor(context: Context, attr: Int): Color {
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
index 4ed7e19f341d..10b004e1b243 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
@@ -29,6 +29,7 @@ import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.SparseIntArray;
@@ -36,6 +37,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -57,6 +59,7 @@ public final class DeviceStateRotationLockSettingsManager {
private final SecureSettings mSecureSettings;
private String[] mDeviceStateRotationLockDefaults;
private SparseIntArray mDeviceStateRotationLockSettings;
+ private SparseIntArray mDeviceStateDefaultRotationLockSettings;
private SparseIntArray mDeviceStateRotationLockFallbackSettings;
private String mLastSettingValue;
private List<SettableDeviceState> mSettableDeviceStates;
@@ -93,9 +96,7 @@ public final class DeviceStateRotationLockSettingsManager {
/** Returns true if device-state based rotation lock settings are enabled. */
public static boolean isDeviceStateRotationLockEnabled(Context context) {
return context.getResources()
- .getStringArray(R.array.config_perDeviceStateRotationLockDefaults)
- .length
- > 0;
+ .getStringArray(R.array.config_perDeviceStateRotationLockDefaults).length > 0;
}
private void listenForSettingsChange() {
@@ -228,6 +229,15 @@ public final class DeviceStateRotationLockSettingsManager {
try {
key = Integer.parseInt(values[i++]);
value = Integer.parseInt(values[i++]);
+ boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED;
+ boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key)
+ == DEVICE_STATE_ROTATION_LOCK_IGNORED;
+ if (isPersistedValueIgnored != isDefaultValueIgnored) {
+ Log.w(TAG, "Conflict for ignored device state " + key
+ + ". Falling back on defaults");
+ fallbackOnDefaults();
+ return;
+ }
mDeviceStateRotationLockSettings.put(key, value);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error deserializing one of the saved settings", e);
@@ -276,6 +286,9 @@ public final class DeviceStateRotationLockSettingsManager {
}
private void persistSettingIfChanged(String newSettingValue) {
+ Log.v(TAG, "persistSettingIfChanged: "
+ + "last=" + mLastSettingValue + ", "
+ + "new=" + newSettingValue);
if (TextUtils.equals(mLastSettingValue, newSettingValue)) {
return;
}
@@ -288,6 +301,8 @@ public final class DeviceStateRotationLockSettingsManager {
private void loadDefaults() {
mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length);
+ mDeviceStateDefaultRotationLockSettings = new SparseIntArray(
+ mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockSettings = new SparseIntArray(
mDeviceStateRotationLockDefaults.length);
mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1);
@@ -311,6 +326,7 @@ public final class DeviceStateRotationLockSettingsManager {
boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED;
mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable));
mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting);
+ mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting);
} catch (NumberFormatException e) {
Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e);
return;
@@ -318,6 +334,22 @@ public final class DeviceStateRotationLockSettingsManager {
}
}
+ /** Dumps internal state. */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("DeviceStateRotationLockSettingsManager");
+ pw.increaseIndent();
+ pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString(
+ mDeviceStateRotationLockDefaults));
+ pw.println("mDeviceStateDefaultRotationLockSettings: "
+ + mDeviceStateDefaultRotationLockSettings);
+ pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings);
+ pw.println("mDeviceStateRotationLockFallbackSettings: "
+ + mDeviceStateRotationLockFallbackSettings);
+ pw.println("mSettableDeviceStates: " + mSettableDeviceStates);
+ pw.println("mLastSettingValue: " + mLastSettingValue);
+ pw.decreaseIndent();
+ }
+
/**
* Called when the persisted settings have changed, requiring a reinitialization of the
* in-memory map.
@@ -372,5 +404,13 @@ public final class DeviceStateRotationLockSettingsManager {
public int hashCode() {
return Objects.hash(mDeviceState, mIsSettable);
}
+
+ @Override
+ public String toString() {
+ return "SettableDeviceState{"
+ + "mDeviceState=" + mDeviceState
+ + ", mIsSettable=" + mIsSettable
+ + '}';
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 78df0f27cc6a..ca88f8da63c9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.app.settings.SettingsEnums
import android.os.Bundle
// Defines the category of the log, for quick filter
@@ -31,20 +32,21 @@ enum class LogCategory {
}
// Defines the log events in Spa.
-enum class LogEvent {
+enum class LogEvent(val action: Int) {
// Page related events.
- PAGE_ENTER,
- PAGE_LEAVE,
+ PAGE_ENTER(SettingsEnums.PAGE_VISIBLE),
+ PAGE_LEAVE(SettingsEnums.PAGE_HIDE),
// Entry related events.
- ENTRY_CLICK,
- ENTRY_SWITCH,
+ ENTRY_CLICK(SettingsEnums.ACTION_SETTINGS_TILE_CLICK),
+ ENTRY_SWITCH(SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE),
}
internal const val LOG_DATA_DISPLAY_NAME = "name"
-internal const val LOG_DATA_SESSION_NAME = "session"
internal const val LOG_DATA_SWITCH_STATUS = "switch"
+const val LOG_DATA_SESSION_NAME = "session"
+
/**
* The interface of logger in Spa
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
index 2c3c2e003832..d8c35a36d061 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -22,9 +22,11 @@ import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+const val SESSION_UNKNOWN = "unknown"
const val SESSION_BROWSE = "browse"
const val SESSION_SEARCH = "search"
const val SESSION_SLICE = "slice"
+const val SESSION_EXTERNAL = "external"
const val KEY_DESTINATION = "spaActivityDestination"
const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 1a3c0ab67b93..47bf85d8417b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -19,6 +19,7 @@ package com.android.settingslib.spaprivileged.template.app
import android.content.Context
import android.content.pm.ApplicationInfo
import android.text.format.Formatter
+import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.produceState
@@ -30,18 +31,26 @@ import com.android.settingslib.spaprivileged.model.app.userHandle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
+private const val TAG = "AppStorageSize"
+
@Composable
fun ApplicationInfo.getStorageSize(): State<String> {
val context = LocalContext.current
return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
withContext(Dispatchers.IO) {
- value = Formatter.formatFileSize(context, calculateSizeBytes(context))
+ val sizeBytes = calculateSizeBytes(context)
+ value = if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else ""
}
}
}
-private fun ApplicationInfo.calculateSizeBytes(context: Context): Long {
+private fun ApplicationInfo.calculateSizeBytes(context: Context): Long? {
val storageStatsManager = context.storageStatsManager
- val stats = storageStatsManager.queryStatsForPackage(storageUuid, packageName, userHandle)
- return stats.codeBytes + stats.dataBytes
+ return try {
+ val stats = storageStatsManager.queryStatsForPackage(storageUuid, packageName, userHandle)
+ stats.codeBytes + stats.dataBytes
+ } catch (e: Exception) {
+ Log.w(TAG, "Failed to query stats: $e")
+ null
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index fcacc34ba881..e3af58702445 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -20,6 +20,7 @@ import android.app.usage.StorageStats
import android.app.usage.StorageStatsManager
import android.content.Context
import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager.NameNotFoundException
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.junit4.createComposeRule
@@ -60,9 +61,11 @@ class AppStorageSizeTest {
@Before
fun setUp() {
whenever(context.storageStatsManager).thenReturn(storageStatsManager)
- whenever(storageStatsManager.queryStatsForPackage(
- app.storageUuid, app.packageName, app.userHandle
- )).thenReturn(STATS)
+ whenever(
+ storageStatsManager.queryStatsForPackage(
+ app.storageUuid, app.packageName, app.userHandle
+ )
+ ).thenReturn(STATS)
}
@Test
@@ -78,6 +81,24 @@ class AppStorageSizeTest {
composeTestRule.waitUntil { storageSize.value == "120 B" }
}
+ @Test
+ fun getStorageSize_throwException() {
+ var storageSize = stateOf("Computing")
+ whenever(
+ storageStatsManager.queryStatsForPackage(
+ app.storageUuid, app.packageName, app.userHandle
+ )
+ ).thenThrow(NameNotFoundException())
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ storageSize = app.getStorageSize()
+ }
+ }
+
+ composeTestRule.waitUntil { storageSize.value == "" }
+ }
+
companion object {
private val STATS = StorageStats().apply {
codeBytes = 100
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index b92b3d665675..8e7d36edb05a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1648,4 +1648,7 @@
<item>Move right</item>
<item>Move up</item>
</string-array>
+
+ <!-- Formatting states for the scale of font size, in percent. Double "%" is required to represent the symbol "%". [CHAR LIMIT=20] -->
+ <string name="font_scale_percentage"> <xliff:g id="percentage">%1$d</xliff:g> %%</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index fb06976ebfe3..3ec5ebad0de6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -23,6 +23,7 @@ import android.widget.Switch;
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
@@ -132,6 +133,11 @@ public class PrimarySwitchPreference extends RestrictedPreference {
}
}
+ @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+ public boolean isSwitchEnabled() {
+ return mEnableSwitch;
+ }
+
/**
* If admin is not null, disables the switch.
* Otherwise, keep it enabled.
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
index 81006dd6b011..0fa15eb6bc0c 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -33,7 +33,10 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager.SettableDeviceState;
+import com.google.common.truth.Expect;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -45,6 +48,8 @@ import java.util.List;
@RunWith(AndroidJUnit4.class)
public class DeviceStateRotationLockSettingsManagerTest {
+ @Rule public Expect mExpect = Expect.create();
+
@Mock private Context mMockContext;
@Mock private Resources mMockResources;
@@ -117,4 +122,40 @@ public class DeviceStateRotationLockSettingsManagerTest {
new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false)
).inOrder();
}
+
+ @Test
+ public void persistedInvalidIgnoredState_returnsDefaults() {
+ when(mMockResources.getStringArray(
+ R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+ new String[]{"0:1", "1:0:2", "2:2"});
+ // Here 2 has IGNORED, and in the defaults 1 has IGNORED.
+ persistSettings("0:2:2:0:1:2");
+ DeviceStateRotationLockSettingsManager manager =
+ new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
+
+ mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(1);
+ mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(2);
+ mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(2);
+ }
+
+ @Test
+ public void persistedValidValues_returnsPersistedValues() {
+ when(mMockResources.getStringArray(
+ R.array.config_perDeviceStateRotationLockDefaults)).thenReturn(
+ new String[]{"0:1", "1:0:2", "2:2"});
+ persistSettings("0:2:1:0:2:1");
+ DeviceStateRotationLockSettingsManager manager =
+ new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings);
+
+ mExpect.that(manager.getRotationLockSetting(0)).isEqualTo(2);
+ mExpect.that(manager.getRotationLockSetting(1)).isEqualTo(1);
+ mExpect.that(manager.getRotationLockSetting(2)).isEqualTo(1);
+ }
+
+ private void persistSettings(String value) {
+ mFakeSecureSettings.putStringForUser(
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK,
+ value,
+ UserHandle.USER_CURRENT);
+ }
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index c9d840a77b80..6a5535d345db 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -97,5 +97,8 @@ public class SystemSettings {
Settings.System.TOUCHPAD_NATURAL_SCROLLING,
Settings.System.TOUCHPAD_TAP_TO_CLICK,
Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+ Settings.System.CAMERA_FLASH_NOTIFICATION,
+ Settings.System.SCREEN_FLASH_NOTIFICATION,
+ Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index c2a3ada53591..3fe12b3dab32 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -16,6 +16,7 @@
package android.provider.settings.validators;
+import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
@@ -215,5 +216,8 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.UNREAD_NOTIFICATION_DOT_INDICATOR, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.AUTO_LAUNCH_MEDIA_CONTROLS, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.LOCALE_PREFERENCES, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(System.CAMERA_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.SCREEN_FLASH_NOTIFICATION_COLOR, ANY_INTEGER_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 1a6920803b03..d49627e5334a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2835,6 +2835,15 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
SystemSettingsProto.Notification.VIBRATION_INTENSITY);
+ dumpSetting(s, p,
+ Settings.System.CAMERA_FLASH_NOTIFICATION,
+ SystemSettingsProto.Notification.CAMERA_FLASH_NOTIFICATION);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_FLASH_NOTIFICATION,
+ SystemSettingsProto.Notification.SCREEN_FLASH_NOTIFICATION);
+ dumpSetting(s, p,
+ Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
+ SystemSettingsProto.Notification.SCREEN_FLASH_NOTIFICATION_COLOR_GLOBAL);
// Settings.System.NOTIFICATIONS_USE_RING_VOLUME intentionally excluded since it's deprecated.
p.end(notificationToken);
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index 140c10da922e..f358417e6bea 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -18,6 +18,15 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// This filegroup is used by menu tests.
+filegroup {
+ name: "AccessibilityMenuSource",
+ srcs: [
+ "src/**/AccessibilityMenuService.java",
+ "src/**/A11yMenuShortcut.java",
+ ],
+}
+
android_app {
name: "AccessibilityMenu",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
index 39e5a8c6876b..a902c5b54b7b 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/grid_item.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/shortcutItem"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="@dimen/grid_item_padding"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index c1f2aa86c6b5..8ca64d2505ce 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.accessibilitymenu;
+import android.Manifest;
import android.accessibilityservice.AccessibilityButtonController;
import android.accessibilityservice.AccessibilityService;
import android.content.BroadcastReceiver;
@@ -51,8 +52,12 @@ import java.util.List;
/** @hide */
public class AccessibilityMenuService extends AccessibilityService
implements View.OnTouchListener {
- private static final String TAG = "A11yMenuService";
+ public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
+ public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
+ public static final String INTENT_HIDE_MENU = ".hide_menu";
+
+ private static final String TAG = "A11yMenuService";
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
private static final int BRIGHTNESS_UP_INCREMENT_GAMMA =
@@ -74,7 +79,8 @@ public class AccessibilityMenuService extends AccessibilityService
// TODO(b/136716947): Support multi-display once a11y framework side is ready.
private DisplayManager mDisplayManager;
- final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() {
+ private final DisplayManager.DisplayListener mDisplayListener =
+ new DisplayManager.DisplayListener() {
int mRotation;
@Override
@@ -95,13 +101,20 @@ public class AccessibilityMenuService extends AccessibilityService
}
};
- final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mHideMenuReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mA11yMenuLayout.hideMenu();
}
};
+ private final BroadcastReceiver mToggleMenuReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mA11yMenuLayout.toggleVisibility();
+ }
+ };
+
/**
* Update a11y menu layout when large button setting is changed.
*/
@@ -172,7 +185,19 @@ public class AccessibilityMenuService extends AccessibilityService
protected void onServiceConnected() {
mA11yMenuLayout = new A11yMenuOverlayLayout(this);
- registerReceiver(mBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ IntentFilter hideMenuFilter = new IntentFilter();
+ hideMenuFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ hideMenuFilter.addAction(PACKAGE_NAME + INTENT_HIDE_MENU);
+
+ // Including WRITE_SECURE_SETTINGS enforces that we only listen to apps
+ // with the restricted WRITE_SECURE_SETTINGS permission who broadcast this intent.
+ registerReceiver(mHideMenuReceiver, hideMenuFilter,
+ Manifest.permission.WRITE_SECURE_SETTINGS, null,
+ Context.RECEIVER_EXPORTED);
+ registerReceiver(mToggleMenuReceiver,
+ new IntentFilter(PACKAGE_NAME + INTENT_TOGGLE_MENU),
+ Manifest.permission.WRITE_SECURE_SETTINGS, null,
+ Context.RECEIVER_EXPORTED);
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mPrefs.registerOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
@@ -260,7 +285,8 @@ public class AccessibilityMenuService extends AccessibilityService
* @param increment The increment amount in gamma-space
*/
private void adjustBrightness(int increment) {
- BrightnessInfo info = getDisplay().getBrightnessInfo();
+ Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ BrightnessInfo info = display.getBrightnessInfo();
int gamma = BrightnessUtils.convertLinearToGammaFloat(
info.brightness,
info.brightnessMinimum,
@@ -275,7 +301,7 @@ public class AccessibilityMenuService extends AccessibilityService
info.brightnessMinimum,
info.brightnessMaximum
);
- mDisplayManager.setBrightness(getDisplayId(), brightness);
+ mDisplayManager.setBrightness(display.getDisplayId(), brightness);
mA11yMenuLayout.showSnackbar(
getString(R.string.brightness_percentage_label,
(gamma / (BrightnessUtils.GAMMA_SPACE_MAX / 100))));
@@ -310,7 +336,8 @@ public class AccessibilityMenuService extends AccessibilityService
@Override
public boolean onUnbind(Intent intent) {
- unregisterReceiver(mBroadcastReceiver);
+ unregisterReceiver(mHideMenuReceiver);
+ unregisterReceiver(mToggleMenuReceiver);
mPrefs.unregisterOnSharedPreferenceChangeListener(mSharedPreferenceChangeListener);
sInitialized = false;
return super.onUnbind(intent);
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index 6f0fe374d6f6..6ae65cb6d8f6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -21,6 +21,7 @@ import android.view.LayoutInflater;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.TextView;
@@ -146,6 +147,15 @@ public class A11yMenuAdapter extends BaseAdapter {
shortcutIconButton.setBackground(
mShortcutDrawableUtils.createAdaptiveIconDrawable(shortcutItem.imageColor));
+
+ shortcutIconButton.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(
+ View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setUniqueId(host.getTag().toString());
+ }
+ });
}
}
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
new file mode 100644
index 000000000000..1757dda84eef
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/Android.bp
@@ -0,0 +1,43 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "AccessibilityMenuServiceTests",
+ certificate: "platform",
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.core",
+ "androidx.test.runner",
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "platform-test-annotations",
+ "truth-prebuilt",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ":AccessibilityMenuSource",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+ instrumentation_for: "AccessibilityMenu",
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..7be6ca742376
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.accessibility.accessibilitymenu.tests">
+
+ <!-- Needed to write to Settings.Secure to enable and disable the service under test. -->
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+
+ <application android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.systemui.accessibility.accessibilitymenu.tests"
+ android:label="AccessibilityMenu Test Cases">
+ </instrumentation>
+</manifest> \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidTest.xml b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidTest.xml
new file mode 100644
index 000000000000..39bee5392720
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs AccessibilityMenu Test Cases.">
+ <option name="test-tag" value="AccessibilityMenuServiceTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="AccessibilityMenuServiceTests.apk" />
+ <option name="aapt-version" value="AAPT2" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.systemui.accessibility.accessibilitymenu.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration> \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/tests/TEST_MAPPING
new file mode 100644
index 000000000000..2bd52b552698
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "AccessibilityMenuServiceTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+} \ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
new file mode 100644
index 000000000000..529a70c1ab18
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.accessibility.accessibilitymenu.tests;
+
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU;
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManager;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.TestUtils;
+import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityMenuServiceTest {
+ private static final String TAG = "A11yMenuServiceTest";
+
+ private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
+ private static final int TIMEOUT_UI_CHANGE_S = 5;
+
+ private static Instrumentation sInstrumentation;
+ private static UiAutomation sUiAutomation;
+
+ private static AccessibilityManager sAccessibilityManager;
+
+ @BeforeClass
+ public static void classSetup() throws Throwable {
+ final String serviceName = PACKAGE_NAME + "/.AccessibilityMenuService";
+ sInstrumentation = InstrumentationRegistry.getInstrumentation();
+ sUiAutomation = sInstrumentation.getUiAutomation(
+ UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+ final Context context = sInstrumentation.getContext();
+ sAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+
+ // Disable all a11yServices if any are active.
+ if (!sAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
+ TestUtils.waitUntil("Failed to disable all services",
+ TIMEOUT_SERVICE_STATUS_CHANGE_S,
+ () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty());
+ }
+
+ // Enable a11yMenu service.
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, serviceName);
+
+ TestUtils.waitUntil("Failed to enable service",
+ TIMEOUT_SERVICE_STATUS_CHANGE_S,
+ () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
+ info -> info.getId().contains(serviceName)).count() == 1);
+ }
+
+ @AfterClass
+ public static void classTeardown() throws Throwable {
+ Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
+ }
+
+ private boolean isMenuVisible() {
+ return sUiAutomation.getRootInActiveWindow() != null
+ && sUiAutomation.getRootInActiveWindow().getPackageName().toString().equals(
+ PACKAGE_NAME);
+ }
+
+ private void openMenu() throws Throwable {
+ if (isMenuVisible()) {
+ return;
+ }
+ Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
+ sInstrumentation.getContext().sendBroadcast(intent);
+ TestUtils.waitUntil("Timed out before menu could appear.",
+ TIMEOUT_UI_CHANGE_S, () -> isMenuVisible());
+ }
+
+ private void closeMenu() throws Throwable {
+ if (!isMenuVisible()) {
+ return;
+ }
+ Intent intent = new Intent(PACKAGE_NAME + INTENT_HIDE_MENU);
+ sInstrumentation.getContext().sendBroadcast(intent);
+ TestUtils.waitUntil("Timed out before menu could close.",
+ TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
+ }
+
+ private List<AccessibilityNodeInfo> getGridButtonList() {
+ return sUiAutomation.getRootInActiveWindow()
+ .findAccessibilityNodeInfosByViewId(PACKAGE_NAME + ":id/shortcutIconBtn");
+ }
+
+ private AccessibilityNodeInfo findGridButtonInfo(
+ List<AccessibilityNodeInfo> buttons, String text) {
+ for (AccessibilityNodeInfo button: buttons) {
+ if (button.getUniqueId().equals(text)) {
+ return button;
+ }
+ }
+ return null;
+ }
+
+ @Test
+ public void testAdjustBrightness() throws Throwable {
+ openMenu();
+
+ Context context = sInstrumentation.getTargetContext();
+ DisplayManager displayManager = context.getSystemService(
+ DisplayManager.class);
+ float resetBrightness = displayManager.getBrightness(context.getDisplayId());
+
+ List<AccessibilityNodeInfo> buttons = getGridButtonList();
+ AccessibilityNodeInfo brightnessUpButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_BRIGHTNESS_UP_VALUE.ordinal()));
+ AccessibilityNodeInfo brightnessDownButton = findGridButtonInfo(buttons,
+ String.valueOf(ShortcutId.ID_BRIGHTNESS_DOWN_VALUE.ordinal()));
+
+ int clickId = AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.getId();
+ BrightnessInfo brightnessInfo = displayManager.getDisplay(
+ context.getDisplayId()).getBrightnessInfo();
+
+ try {
+ displayManager.setBrightness(context.getDisplayId(), brightnessInfo.brightnessMinimum);
+ TestUtils.waitUntil("Could not change to minimum brightness",
+ TIMEOUT_UI_CHANGE_S,
+ () -> displayManager.getBrightness(context.getDisplayId())
+ == brightnessInfo.brightnessMinimum);
+ brightnessUpButton.performAction(clickId);
+ TestUtils.waitUntil("Did not detect an increase in brightness.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> displayManager.getBrightness(context.getDisplayId())
+ > brightnessInfo.brightnessMinimum);
+
+ displayManager.setBrightness(context.getDisplayId(), brightnessInfo.brightnessMaximum);
+ TestUtils.waitUntil("Could not change to maximum brightness",
+ TIMEOUT_UI_CHANGE_S,
+ () -> displayManager.getBrightness(context.getDisplayId())
+ == brightnessInfo.brightnessMaximum);
+ brightnessDownButton.performAction(clickId);
+ TestUtils.waitUntil("Did not detect a decrease in brightness.",
+ TIMEOUT_UI_CHANGE_S,
+ () -> displayManager.getBrightness(context.getDisplayId())
+ < brightnessInfo.brightnessMaximum);
+ } finally {
+ displayManager.setBrightness(context.getDisplayId(), resetBrightness);
+ closeMenu();
+ }
+ }
+}
diff --git a/packages/SystemUI/animation/.gitignore b/packages/SystemUI/animation/.gitignore
new file mode 100644
index 000000000000..f9a33dbbcc7e
--- /dev/null
+++ b/packages/SystemUI/animation/.gitignore
@@ -0,0 +1,9 @@
+.idea/
+.gradle/
+gradle/
+build/
+gradlew*
+local.properties
+*.iml
+android.properties
+buildSrc \ No newline at end of file
diff --git a/packages/SystemUI/animation/build.gradle b/packages/SystemUI/animation/build.gradle
new file mode 100644
index 000000000000..939455fa44ac
--- /dev/null
+++ b/packages/SystemUI/animation/build.gradle
@@ -0,0 +1,37 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+// TODO: Pull out surfaceeffects outside of src and have separate build files there.
+android {
+ sourceSets {
+ main {
+ java.srcDirs = ["${SYS_UI_DIR}/animation/src/com/android/systemui/surfaceeffects/"]
+ manifest.srcFile "${SYS_UI_DIR}/animation/AndroidManifest.xml"
+ }
+ }
+
+ compileSdk 33
+
+ defaultConfig {
+ minSdk 33
+ targetSdk 33
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+ tasks.lint.enabled = false
+ tasks.withType(JavaCompile) {
+ options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ freeCompilerArgs = ["-Xjvm-default=all"]
+ }
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0"
+ implementation 'androidx.core:core-animation:1.0.0-alpha02'
+ implementation 'androidx.core:core-ktx:1.9.0'
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 17a94b8639d0..296c2ae5cf99 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -419,7 +419,7 @@ class ActivityLaunchAnimator(
internal val delegate: AnimationDelegate
init {
- delegate = AnimationDelegate(controller, callback, launchAnimator, listener)
+ delegate = AnimationDelegate(controller, callback, listener, launchAnimator)
}
@BinderThread
@@ -446,10 +446,10 @@ class ActivityLaunchAnimator(
constructor(
private val controller: Controller,
private val callback: Callback,
- /** The animator to use to animate the window launch. */
- private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR,
/** Listener for animation lifecycle events. */
- private val listener: Listener? = null
+ private val listener: Listener? = null,
+ /** The animator to use to animate the window launch. */
+ private val launchAnimator: LaunchAnimator = DEFAULT_LAUNCH_ANIMATOR
) : RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
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
new file mode 100644
index 000000000000..f64ea4561906
--- /dev/null
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.systemui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+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 org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UElement
+
+class DemotingTestWithoutBugDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes(): List<Class<out UElement>> {
+ return listOf(UAnnotation::class.java)
+ }
+
+ override fun createUastHandler(context: JavaContext): UElementHandler {
+ return object : UElementHandler() {
+ override fun visitAnnotation(node: UAnnotation) {
+ if (node.qualifiedName !in DEMOTING_ANNOTATION) {
+ return
+ }
+ 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)
+ }
+ }
+ }
+ }
+
+ companion object {
+ val DEMOTING_ANNOTATION =
+ listOf("androidx.test.filters.FlakyTest", "android.platform.test.annotations.FlakyTest")
+
+ @JvmField
+ val ISSUE: Issue =
+ Issue.create(
+ id = "DemotingTestWithoutBug",
+ briefDescription = "Demoting a test without attaching a bug.",
+ explanation =
+ """
+ Annotations (`@FlakyTest`) demote tests to an unmonitored \
+ test suite. Please set the `bugId` field in such annotations to track \
+ the test status.
+ """,
+ category = Category.TESTING,
+ priority = 8,
+ severity = Severity.WARNING,
+ implementation =
+ Implementation(
+ DemotingTestWithoutBugDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
index 84f70502fa45..387b67d231cd 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt
@@ -39,7 +39,8 @@ class SystemUIIssueRegistry : IssueRegistry() {
RegisterReceiverViaContextDetector.ISSUE,
SoftwareBitmapDetector.ISSUE,
NonInjectedServiceDetector.ISSUE,
- StaticSettingsProviderDetector.ISSUE
+ StaticSettingsProviderDetector.ISSUE,
+ DemotingTestWithoutBugDetector.ISSUE
)
override val api: Int
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
new file mode 100644
index 000000000000..557c300635eb
--- /dev/null
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -0,0 +1,151 @@
+/*
+ * 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.systemui.lint
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+class DemotingTestWithoutBugDetectorTest : SystemUILintDetectorTest() {
+
+ override fun getDetector(): Detector = DemotingTestWithoutBugDetector()
+ override fun getIssues(): List<Issue> = listOf(DemotingTestWithoutBugDetector.ISSUE)
+
+ @Test
+ fun testMarkFlaky_withBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import androidx.test.filters.FlakyTest;
+
+ @FlakyTest(bugId = 123)
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.annotations.FlakyTest;
+
+ @FlakyTest(bugId = 123)
+ public class TestClass {
+ public void testCase() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .issues(DemotingTestWithoutBugDetector.ISSUE)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun testMarkFlaky_withoutBugId() {
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import androidx.test.filters.FlakyTest;
+
+ @FlakyTest
+ 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]
+ @FlakyTest
+ ~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+
+ lint()
+ .files(
+ TestFiles.java(
+ """
+ package test.pkg;
+ import android.platform.test.annotations.FlakyTest;
+
+ @FlakyTest
+ 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]
+ @FlakyTest
+ ~~~~~~~~~~
+ 0 errors, 1 warnings
+ """
+ )
+ }
+
+ private val filtersFlakyTestStub: TestFile =
+ java(
+ """
+ package androidx.test.filters;
+
+ public @interface FlakyTest {
+ int bugId() default -1;
+ }
+ """
+ )
+ private val annotationsFlakyTestStub: TestFile =
+ java(
+ """
+ package android.platform.test.annotations;
+
+ public @interface FlakyTest {
+ int bugId() default -1;
+ }
+ """
+ )
+ private val stubs = arrayOf(filtersFlakyTestStub, annotationsFlakyTestStub)
+}
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index b6a78f56ec5f..caf32331b81f 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -82,7 +82,7 @@
<!-- The vertical margin between the date and the owner info. -->
<!-- The translation for disappearing security views after having solved them. -->
- <dimen name="disappear_y_translation">-32dp</dimen>
+ <dimen name="disappear_y_translation">-50dp</dimen>
<!-- Dimens for animation for the Bouncer PIN view -->
<dimen name="pin_view_trans_y_entry">120dp</dimen>
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml
index 58fe368ce4e1..97bd18e0442b 100644
--- a/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_background.xml
@@ -27,7 +27,7 @@
<shape android:shape="rectangle">
<corners android:radius="@dimen/magnifier_outer_corner_radius" />
<stroke
- android:color="@android:color/black"
+ android:color="@color/magnification_drag_handle_stroke"
android:width="@dimen/magnifier_stroke_width"/>
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml
index a52e8053d8a0..66617e16b33a 100644
--- a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background.xml
@@ -16,7 +16,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
- android:color="@android:color/black"
+ android:color="@color/magnification_drag_handle_stroke"
android:width="@dimen/magnifier_stroke_width"/>
<corners android:radius="@dimen/magnifier_corner_radius" />
<solid android:color="@color/magnification_border_color" />
diff --git a/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_change.xml b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_change.xml
new file mode 100644
index 000000000000..e367f50632a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/accessibility_window_magnification_drag_handle_background_change.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?><!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:color="@color/magnification_border_color"
+ android:width="@dimen/magnifier_stroke_width"/>
+ <corners android:radius="@dimen/magnifier_corner_radius" />
+ <solid android:color="@color/magnification_drag_handle_background_change" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_move_magnification.xml b/packages/SystemUI/res/drawable/ic_move_magnification.xml
index 641bb4381fc8..079600734bc8 100644
--- a/packages/SystemUI/res/drawable/ic_move_magnification.xml
+++ b/packages/SystemUI/res/drawable/ic_move_magnification.xml
@@ -20,6 +20,6 @@
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
- android:fillColor="@color/magnification_drag_handle_tint"
+ android:fillColor="@color/magnification_drag_handle_stroke"
android:pathData="M12,15Q10.75,15 9.875,14.125Q9,13.25 9,12Q9,10.75 9.875,9.875Q10.75,9 12,9Q13.25,9 14.125,9.875Q15,10.75 15,12Q15,13.25 14.125,14.125Q13.25,15 12,15ZM12,22 L7.75,17.75 9.15,16.35 12,19.15 14.85,16.35 16.25,17.75ZM6.25,16.25 L2,12 6.25,7.75 7.65,9.15 4.85,12 7.65,14.85ZM9.15,7.65 L7.75,6.25 12,2 16.25,6.25 14.85,7.65 12,4.85ZM17.75,16.25 L16.35,14.85 19.15,12 16.35,9.15 17.75,7.75 22,12Z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml b/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml
new file mode 100644
index 000000000000..de0a6201cb09
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_media_rec_scrim.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <!-- gradient from 25% in the center to 100% at edges -->
+ <gradient
+ android:type="radial"
+ android:gradientRadius="40%p"
+ android:startColor="#AE000000"
+ android:endColor="#00000000" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_recommendation_view.xml b/packages/SystemUI/res/layout/media_recommendation_view.xml
index c54c4e48d13d..a4aeba1dbcd6 100644
--- a/packages/SystemUI/res/layout/media_recommendation_view.xml
+++ b/packages/SystemUI/res/layout/media_recommendation_view.xml
@@ -22,9 +22,10 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:translationZ="0dp"
- android:scaleType="centerCrop"
+ android:scaleType="matrix"
android:adjustViewBounds="true"
android:clipToOutline="true"
+ android:layerType="hardware"
android:background="@drawable/bg_smartspace_media_item"/>
<!-- App icon -->
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index 7dfe7c4c72be..ae0f8f46599d 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -21,7 +21,9 @@
android:layout_height="wrap_content"
android:background="@drawable/accessibility_magnification_setting_view_bg"
android:orientation="vertical"
- android:padding="@dimen/magnification_setting_background_padding">
+ android:padding="@dimen/magnification_setting_background_padding"
+ android:focusable="true"
+ android:contentDescription="@string/accessibility_magnification_settings_panel_description">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index c6cc0bc01116..d4ebd100a91d 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -163,8 +163,8 @@
<color name="magnification_border_color">#F29900</color>
<color name="magnification_switch_button_color">#7F000000</color>
<color name="magnification_drag_corner_background">#E5FFFFFF</color>
- <color name="magnification_drag_handle_color">#B3000000</color>
- <color name="magnification_drag_handle_tint">#111111</color>
+ <color name="magnification_drag_handle_stroke">#000000</color>
+ <color name="magnification_drag_handle_background_change">#111111</color>
<color name="accessibility_magnifier_bg">#FCFCFC</color>
<color name="accessibility_magnifier_bg_stroke">#E0E0E0</color>
<color name="accessibility_magnifier_icon_color">#252525</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e65c327736e1..8f90724c09b9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -782,7 +782,7 @@
<!-- Duration in milliseconds of the dream in complications fade-in animation. -->
<integer name="config_dreamOverlayInComplicationsDurationMs">250</integer>
<!-- Duration in milliseconds of the y-translation animation when entering a dream -->
- <integer name="config_dreamOverlayInTranslationYDurationMs">917</integer>
+ <integer name="config_dreamOverlayInTranslationYDurationMs">1167</integer>
<!-- Delay in milliseconds before switching to the dock user and dreaming if a secondary user is
active when the device is locked and docked. 0 indicates disabled. Default is 1 minute. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aba3fc4615c9..0f2ce444f225 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -196,9 +196,6 @@
<!-- Increased height of a small notification in the status bar -->
<dimen name="notification_min_height_increased">146dp</dimen>
- <!-- Increased height of a collapsed media notification in the status bar -->
- <dimen name="notification_min_height_media">160dp</dimen>
-
<!-- Height of a small notification in the status bar which was used before android N -->
<dimen name="notification_min_height_legacy">64dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4d989a67bffd..f4b3b87aab16 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2337,6 +2337,8 @@
<string name="magnification_mode_switch_state_window">Magnify part of screen</string>
<!-- Click action label for magnification switch. [CHAR LIMIT=NONE] -->
<string name="magnification_mode_switch_click_label">Switch</string>
+ <!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
+ <string name="magnification_open_settings_click_label">Open magnification settings</string>
<!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
<string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
@@ -2358,6 +2360,8 @@
<!-- Description of the window magnification Bottom handle [CHAR LIMIT=NONE]-->
<string name="accessibility_magnification_bottom_handle">Bottom handle</string>
+ <!-- Description of the window magnification panel [CHAR LIMIT=NONE]-->
+ <string name="accessibility_magnification_settings_panel_description">Magnification settings</string>
<!-- Title of the window magnification panel option Magnifier size [CHAR LIMIT=NONE]-->
<string name="accessibility_magnifier_size">Magnifier size</string>
<!-- Title of the window magnification panel option Zoom [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 6dd359cb6351..45a5ce34f830 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -18,7 +18,6 @@ package com.android.systemui.shared.system;
import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
import static android.app.ActivityTaskManager.getService;
import android.annotation.NonNull;
@@ -45,6 +44,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
+import android.view.Display;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
@@ -112,6 +112,13 @@ public class ActivityManagerWrapper {
}
/**
+ * @see #getRunningTasks(boolean , int)
+ */
+ public ActivityManager.RunningTaskInfo[] getRunningTasks(boolean filterOnlyVisibleRecents) {
+ return getRunningTasks(filterOnlyVisibleRecents, Display.INVALID_DISPLAY);
+ }
+
+ /**
* We ask for {@link #NUM_RECENT_ACTIVITIES_REQUEST} activities because when in split screen,
* we'll get back 2 activities for each split app and one for launcher. Launcher might be more
* "recently" used than one of the split apps so if we only request 2 tasks, then we might miss
@@ -120,10 +127,12 @@ public class ActivityManagerWrapper {
* @return an array of up to {@link #NUM_RECENT_ACTIVITIES_REQUEST} running tasks
* filtering only for tasks that can be visible in the recent tasks list.
*/
- public ActivityManager.RunningTaskInfo[] getRunningTasks(boolean filterOnlyVisibleRecents) {
+ public ActivityManager.RunningTaskInfo[] getRunningTasks(boolean filterOnlyVisibleRecents,
+ int displayId) {
// Note: The set of running tasks from the system is ordered by recency
List<ActivityManager.RunningTaskInfo> tasks =
- mAtm.getTasks(NUM_RECENT_ACTIVITIES_REQUEST, filterOnlyVisibleRecents);
+ mAtm.getTasks(NUM_RECENT_ACTIVITIES_REQUEST,
+ filterOnlyVisibleRecents, /* keepInExtras= */ false, displayId);
return tasks.toArray(new RunningTaskInfo[tasks.size()]);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index fca55b1c69b4..6f7d66d03cab 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.TRANSIT_SLEEP;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -46,6 +47,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.util.TransitionUtil;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* Helper class to build {@link RemoteTransition} objects
@@ -205,6 +207,12 @@ public class RemoteTransitionCompat {
@SuppressLint("NewApi")
boolean merge(TransitionInfo info, SurfaceControl.Transaction t) {
+ if (info.getType() == TRANSIT_SLEEP) {
+ // A sleep event means we need to stop animations immediately, so cancel here.
+ mListener.onAnimationCanceled(new HashMap<>());
+ finish(mWillFinishToHome, false /* userLeaveHint */);
+ return false;
+ }
ArrayList<TransitionInfo.Change> openingTasks = null;
ArrayList<TransitionInfo.Change> closingTasks = null;
mAppearedTargets = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index fe8b8c944d13..c98e9b40e7ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -40,7 +40,7 @@ data class KeyguardFaceListenModel(
var keyguardGoingAway: Boolean = false,
var listeningForFaceAssistant: Boolean = false,
var occludingAppRequestingFaceAuth: Boolean = false,
- val postureAllowsListening: Boolean = false,
+ var postureAllowsListening: Boolean = false,
var primaryUser: Boolean = false,
var secureCameraLaunched: Boolean = false,
var supportsDetect: Boolean = false,
@@ -70,6 +70,7 @@ data class KeyguardFaceListenModel(
listeningForFaceAssistant.toString(),
occludingAppRequestingFaceAuth.toString(),
primaryUser.toString(),
+ postureAllowsListening.toString(),
secureCameraLaunched.toString(),
supportsDetect.toString(),
switchingUser.toString(),
@@ -109,6 +110,7 @@ data class KeyguardFaceListenModel(
listeningForFaceAssistant = model.listeningForFaceAssistant
occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth
primaryUser = model.primaryUser
+ postureAllowsListening = model.postureAllowsListening
secureCameraLaunched = model.secureCameraLaunched
supportsDetect = model.supportsDetect
switchingUser = model.switchingUser
@@ -152,6 +154,7 @@ data class KeyguardFaceListenModel(
"listeningForFaceAssistant",
"occludingAppRequestingFaceAuth",
"primaryUser",
+ "postureAllowsListening",
"secureCameraLaunched",
"supportsDetect",
"switchingUser",
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 29496169e04f..66d5d097ab04 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -39,7 +39,6 @@ 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;
@@ -1068,13 +1067,10 @@ 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);
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mView, View.ALPHA, 0f);
-
- anims.setInterpolator(Interpolators.STANDARD_ACCELERATE);
- anims.playTogether(alphaAnim, yAnim);
- anims.start();
+ yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE);
+ yAnim.setDuration(500);
+ yAnim.start();
}
private void setupUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index f1abdc68f97e..06258b20e06f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -637,12 +637,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void startAppearAnimation() {
if (mCurrentSecurityMode != SecurityMode.None) {
- mView.setAlpha(1f);
+ setAlpha(1f);
mView.startAppearAnimation(mCurrentSecurityMode);
getCurrentSecurityController().startAppearAnimation();
}
}
+ /** Set the alpha of the security container view */
+ public void setAlpha(float alpha) {
+ mView.setAlpha(alpha);
+ }
+
public boolean startDisappearAnimation(Runnable onFinishRunnable) {
boolean didRunAnimation = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 866b502e00ac..30e2a0b613ee 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -160,6 +160,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.Assert;
import com.android.systemui.util.settings.SecureSettings;
@@ -368,7 +369,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final FaceManager mFaceManager;
private final LockPatternUtils mLockPatternUtils;
@VisibleForTesting
- @DevicePostureController.DevicePostureInt
+ @DevicePostureInt
protected int mConfigFaceAuthSupportedPosture;
private KeyguardBypassController mKeyguardBypassController;
@@ -876,7 +877,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
mBackgroundExecutor.execute(
- () -> mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId));
+ () -> {
+ mLogger.logReportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+ mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+ });
}
private void handleFingerprintAuthFailed() {
@@ -1862,10 +1866,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final DevicePostureController.Callback mPostureCallback =
new DevicePostureController.Callback() {
@Override
- public void onPostureChanged(int posture) {
+ public void onPostureChanged(@DevicePostureInt int posture) {
+ boolean currentPostureAllowsFaceAuth = doesPostureAllowFaceAuth(mPostureState);
+ boolean newPostureAllowsFaceAuth = doesPostureAllowFaceAuth(posture);
mPostureState = posture;
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_UPDATED_POSTURE_CHANGED);
+ if (currentPostureAllowsFaceAuth && !newPostureAllowsFaceAuth) {
+ mLogger.d("New posture does not allow face auth, stopping it");
+ updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_UPDATED_POSTURE_CHANGED);
+ }
}
};
@@ -2524,11 +2533,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// If this message exists, we should not authenticate again until this message is
// consumed by the handler
if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
+ mLogger.logHandlerHasAuthContinueMsgs(action);
return;
}
// don't start running fingerprint until they're registered
if (!mAuthController.areAllFingerprintAuthenticatorsRegistered()) {
+ mLogger.d("All FP authenticators not registered, skipping FP listening state update");
return;
}
final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
@@ -2901,9 +2912,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean biometricEnabledForUser = mBiometricEnabledForUser.get(user);
final boolean shouldListenForFaceAssistant = shouldListenForFaceAssistant();
final boolean isUdfpsFingerDown = mAuthController.isUdfpsFingerDown();
- final boolean isPostureAllowedForFaceAuth =
- mConfigFaceAuthSupportedPosture == 0 /* DEVICE_POSTURE_UNKNOWN */ ? true
- : (mPostureState == mConfigFaceAuthSupportedPosture);
+ final boolean isPostureAllowedForFaceAuth = doesPostureAllowFaceAuth(mPostureState);
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
// instance of KeyguardUpdateMonitor for each user but KeyguardUpdateMonitor is user-aware.
final boolean shouldListen =
@@ -2952,6 +2961,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return shouldListen;
}
+ private boolean doesPostureAllowFaceAuth(@DevicePostureInt int posture) {
+ return mConfigFaceAuthSupportedPosture == DEVICE_POSTURE_UNKNOWN
+ || (posture == mConfigFaceAuthSupportedPosture);
+ }
+
private void logListenerModelData(@NonNull KeyguardListenModel model) {
mLogger.logKeyguardListenerModel(model);
if (model instanceof KeyguardFingerprintListenModel) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
index bc0bd8c53d26..20f90072161b 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt
@@ -16,6 +16,7 @@
package com.android.keyguard.logging
+import android.hardware.biometrics.BiometricSourceType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.dagger.BiometricLog
import com.android.systemui.plugins.log.LogBuffer
@@ -157,6 +158,36 @@ class BiometricUnlockLogger @Inject constructor(@BiometricLog private val logBuf
}
)
}
+
+ fun deferringAuthenticationDueToSleep(
+ userId: Int,
+ biometricSourceType: BiometricSourceType,
+ alreadyPendingAuth: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ str1 = biometricSourceType.name
+ bool2 = alreadyPendingAuth
+ },
+ {
+ "onBiometricAuthenticated, deferring auth: userId: $int1, " +
+ "biometricSourceType: $str1, " +
+ "goingToSleep: true, " +
+ "mPendingAuthentication != null: $bool2"
+ }
+ )
+ }
+
+ fun finishedGoingToSleepWithPendingAuth() {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ "onFinishedGoingToSleep with pendingAuthenticated != null"
+ )
+ }
}
private fun wakeAndUnlockModeToString(mode: Int): String {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 379c78ad8d0e..51aca070b180 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -16,6 +16,7 @@
package com.android.keyguard.logging
+import com.android.systemui.biometrics.AuthRippleController
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController
import com.android.systemui.log.dagger.KeyguardLog
import com.android.systemui.plugins.log.LogBuffer
@@ -120,4 +121,29 @@ constructor(
"type=${KeyguardIndicationRotateTextViewController.indicationTypeToString(type)}"
}
}
+
+ fun notShowingUnlockRipple(keyguardNotShowing: Boolean, unlockNotAllowed: Boolean) {
+ buffer.log(
+ AuthRippleController.TAG,
+ LogLevel.DEBUG,
+ {
+ bool1 = keyguardNotShowing
+ bool2 = unlockNotAllowed
+ },
+ { "Not showing unlock ripple: keyguardNotShowing: $bool1, unlockNotAllowed: $bool2" }
+ )
+ }
+
+ fun showingUnlockRippleAt(x: Int, y: Int, context: String) {
+ buffer.log(
+ AuthRippleController.TAG,
+ LogLevel.DEBUG,
+ {
+ int1 = x
+ int2 = y
+ str1 = context
+ },
+ { "Showing unlock ripple with center (x, y): ($int1, $int2), context: $str1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index fb2c02ad8c48..2403d1116360 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -584,6 +584,30 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
)
}
+ fun logReportSuccessfulBiometricUnlock(isStrongBiometric: Boolean, userId: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ bool1 = isStrongBiometric
+ int1 = userId
+ },
+ { "reporting successful biometric unlock: isStrongBiometric: $bool1, userId: $int1" }
+ )
+ }
+
+ fun logHandlerHasAuthContinueMsgs(action: Int) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { int1 = action },
+ {
+ "MSG_BIOMETRIC_AUTHENTICATION_CONTINUE already queued up, " +
+ "ignoring updating FP listening state to $int1"
+ }
+ )
+ }
+
fun logFaceEnrolledUpdated(oldValue: Boolean, newValue: Boolean) {
logBuffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index b111e1f72f1d..d811d30b5693 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -37,6 +37,8 @@ import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
@@ -1398,6 +1400,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mWindowMagnifierCallback.onPerformScaleAction(mDisplayId,
A11Y_ACTION_SCALE_RANGE.clamp(scale));
}
+
+ @Override
+ public void onSettingsPanelVisibilityChanged(boolean shown) {
+ updateDragHandleResourcesIfNeeded(/* settingsPanelIsShown= */ shown);
+ }
};
@Override
@@ -1436,6 +1443,20 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
}
+ private void updateDragHandleResourcesIfNeeded(boolean settingsPanelIsShown) {
+ mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown
+ ? R.drawable.accessibility_window_magnification_drag_handle_background_change
+ : R.drawable.accessibility_window_magnification_drag_handle_background));
+
+ PorterDuffColorFilter filter = new PorterDuffColorFilter(
+ mContext.getColor(settingsPanelIsShown
+ ? R.color.magnification_border_color
+ : R.color.magnification_drag_handle_stroke),
+ PorterDuff.Mode.SRC_ATOP);
+
+ mDragView.setColorFilter(filter);
+ }
+
private void animateBounceEffect() {
final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView,
PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1),
@@ -1468,7 +1489,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
super.onInitializeAccessibilityNodeInfo(host, info);
final AccessibilityAction clickAction = new AccessibilityAction(
AccessibilityAction.ACTION_CLICK.getId(), mContext.getResources().getString(
- R.string.magnification_mode_switch_click_label));
+ R.string.magnification_open_settings_click_label));
info.addAction(clickAction);
info.setClickable(true);
info.addAction(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 15264e64a241..9ad64e293fe5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -215,9 +215,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
private boolean performA11yAction(View view, int action) {
final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
- if (action == AccessibilityAction.ACTION_CLICK.getId()) {
- handleSingleTap(view);
- } else if (action == R.id.accessibility_action_move_up) {
+ if (action == R.id.accessibility_action_move_up) {
moveButton(0, -windowBounds.height());
} else if (action == R.id.accessibility_action_move_down) {
moveButton(0, windowBounds.height());
@@ -264,8 +262,6 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
} else if (id == R.id.magnifier_full_button) {
hideSettingPanel();
toggleMagnificationMode();
- } else {
- hideSettingPanel();
}
}
};
@@ -273,7 +269,6 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
@Override
public boolean onSingleTap(View view) {
mSingleTapDetected = true;
- handleSingleTap(view);
return true;
}
@@ -328,6 +323,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
}
mContext.unregisterReceiver(mScreenOffReceiver);
+ mCallback.onSettingsPanelVisibilityChanged(/* shown= */ false);
}
public void showSettingPanel() {
@@ -358,10 +354,15 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
}
mWindowManager.addView(mSettingView, mParams);
+ if (resetPosition) {
+ // Request focus on the settings panel when position of the panel is reset.
+ mSettingView.requestFocus();
+ }
// Exclude magnification switch button from system gesture area.
setSystemGestureExclusion();
mIsVisible = true;
+ mCallback.onSettingsPanelVisibilityChanged(/* shown= */ true);
}
mContext.registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
@@ -385,8 +386,8 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
mSettingView = (LinearLayout) View.inflate(mContext,
R.layout.window_magnification_settings_view, null);
- mSettingView.setClickable(true);
mSettingView.setFocusable(true);
+ mSettingView.setFocusableInTouchMode(true);
mSettingView.setOnTouchListener(this::onTouch);
mPanelView = mSettingView.findViewById(R.id.magnifier_panel_view);
@@ -499,22 +500,6 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
}
}
- private void handleSingleTap(View view) {
- int id = view.getId();
- if (id == R.id.magnifier_small_button) {
- setMagnifierSize(MagnificationSize.SMALL);
- } else if (id == R.id.magnifier_medium_button) {
- setMagnifierSize(MagnificationSize.MEDIUM);
- } else if (id == R.id.magnifier_large_button) {
- setMagnifierSize(MagnificationSize.LARGE);
- } else if (id == R.id.magnifier_full_button) {
- hideSettingPanel();
- toggleMagnificationMode();
- } else {
- hideSettingPanel();
- }
- }
-
public void editMagnifierSizeMode(boolean enable) {
setEditMagnifierSizeMode(enable);
updateSelectedButton(MagnificationSize.NONE);
@@ -551,7 +536,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
- LayoutParams.FLAG_NOT_FOCUSABLE,
+ /* _flags= */ 0,
PixelFormat.TRANSPARENT);
params.gravity = Gravity.TOP | Gravity.START;
params.accessibilityTitle = getAccessibilityWindowTitle(context);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
index 22ec65001101..1d833402b1f4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettingsCallback.java
@@ -61,4 +61,11 @@ public interface WindowMagnificationSettingsCallback {
* 1 : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN, 2 : ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
*/
void onModeSwitch(int newMode);
+
+ /**
+ * Called when the visibility of the magnification settings panel changed.
+ *
+ * @param shown The visibility of the magnification settings panel.
+ */
+ void onSettingsPanelVisibilityChanged(boolean shown);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
index 54f933ae6d09..53a421d9eccc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt
@@ -29,6 +29,7 @@ import com.android.systemui.R
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.settings.SystemSettings
+import kotlin.math.roundToInt
/** The Dialog that contains a seekbar for changing the font size. */
class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) :
@@ -56,6 +57,16 @@ class FontScalingDialog(context: Context, private val systemSettings: SystemSett
doneButton = requireViewById(com.android.internal.R.id.button1)
seekBarWithIconButtonsView = requireViewById(R.id.font_scaling_slider)
+ val labelArray = arrayOfNulls<String>(strEntryValues.size)
+ for (i in strEntryValues.indices) {
+ labelArray[i] =
+ context.resources.getString(
+ com.android.settingslib.R.string.font_scale_percentage,
+ (strEntryValues[i].toFloat() * 100).roundToInt()
+ )
+ }
+ seekBarWithIconButtonsView.setProgressStateLabels(labelArray)
+
seekBarWithIconButtonsView.setMax((strEntryValues).size - 1)
val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index d561cd7af7f0..93b57dc127dd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -26,6 +26,7 @@ import android.hardware.biometrics.BiometricSourceType
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.logging.KeyguardLogger
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
@@ -74,6 +75,7 @@ class AuthRippleController @Inject constructor(
private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController,
private val featureFlags: FeatureFlags,
+ private val logger: KeyguardLogger,
rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
WakefulnessLifecycle.Observer {
@@ -120,8 +122,11 @@ class AuthRippleController @Inject constructor(
}
fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
- if (!keyguardStateController.isShowing ||
- !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(biometricSourceType)) {
+ val keyguardNotShowing = !keyguardStateController.isShowing
+ val unlockNotAllowed = !keyguardUpdateMonitor
+ .isUnlockingWithBiometricAllowed(biometricSourceType)
+ if (keyguardNotShowing || unlockNotAllowed) {
+ logger.notShowingUnlockRipple(keyguardNotShowing, unlockNotAllowed)
return
}
@@ -138,6 +143,7 @@ class AuthRippleController @Inject constructor(
Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y)
)
)
+ logger.showingUnlockRippleAt(it.x, it.y, "FP sensor radius: $udfpsRadius")
showUnlockedRipple()
}
} else if (biometricSourceType == BiometricSourceType.FACE) {
@@ -155,6 +161,7 @@ class AuthRippleController @Inject constructor(
Math.max(it.y, centralSurfaces.displayHeight.toInt() - it.y)
)
)
+ logger.showingUnlockRippleAt(it.x, it.y, "Face unlock ripple")
showUnlockedRipple()
}
}
@@ -391,5 +398,6 @@ class AuthRippleController @Inject constructor(
companion object {
const val RIPPLE_ANIMATION_DURATION: Long = 1533
+ const val TAG = "AuthRippleController"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index bc0f9950f865..f83885b7bb32 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -38,6 +38,7 @@ import javax.inject.Inject;
public class FalsingDataProvider {
private static final long MOTION_EVENT_AGE_MS = 1000;
+ private static final long DROP_EVENT_THRESHOLD_MS = 50;
private static final float THREE_HUNDRED_SIXTY_DEG = (float) (2 * Math.PI);
private final int mWidthPixels;
@@ -60,6 +61,7 @@ public class FalsingDataProvider {
private float mAngle = 0;
private MotionEvent mFirstRecentMotionEvent;
private MotionEvent mLastMotionEvent;
+ private boolean mDropLastEvent;
private boolean mJustUnlockedWithFace;
private boolean mA11YAction;
@@ -95,6 +97,12 @@ public class FalsingDataProvider {
// Ensure prior gesture was completed. May be a no-op.
completePriorGesture();
}
+
+ // Drop the gesture closing event if it is close in time to a previous ACTION_MOVE event.
+ // The reason is that the closing ACTION_UP event of a swipe can be a bit offseted from the
+ // previous ACTION_MOVE event and when it happens, it makes some classifiers fail.
+ mDropLastEvent = shouldDropEvent(motionEvent);
+
mRecentMotionEvents.addAll(motionEvents);
FalsingClassifier.logVerbose("Size: " + mRecentMotionEvents.size());
@@ -129,6 +137,7 @@ public class FalsingDataProvider {
mPriorMotionEvents = mRecentMotionEvents;
mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
}
+ mDropLastEvent = false;
mA11YAction = false;
}
@@ -150,8 +159,18 @@ public class FalsingDataProvider {
return mYdpi;
}
+ /**
+ * Get the {@link MotionEvent}s of the most recent gesture.
+ *
+ * Note that this list may not include the last recorded event.
+ * @see #mDropLastEvent
+ */
public List<MotionEvent> getRecentMotionEvents() {
- return mRecentMotionEvents;
+ if (!mDropLastEvent || mRecentMotionEvents.isEmpty()) {
+ return mRecentMotionEvents;
+ } else {
+ return mRecentMotionEvents.subList(0, mRecentMotionEvents.size() - 1);
+ }
}
public List<MotionEvent> getPriorMotionEvents() {
@@ -169,7 +188,12 @@ public class FalsingDataProvider {
return mFirstRecentMotionEvent;
}
- /** Get the last recorded {@link MotionEvent}. */
+ /**
+ * Get the last {@link MotionEvent} of the most recent gesture.
+ *
+ * Note that this may be the event prior to the last recorded event.
+ * @see #mDropLastEvent
+ */
public MotionEvent getLastMotionEvent() {
recalculateData();
return mLastMotionEvent;
@@ -236,12 +260,13 @@ public class FalsingDataProvider {
return;
}
- if (mRecentMotionEvents.isEmpty()) {
+ List<MotionEvent> recentMotionEvents = getRecentMotionEvents();
+ if (recentMotionEvents.isEmpty()) {
mFirstRecentMotionEvent = null;
mLastMotionEvent = null;
} else {
- mFirstRecentMotionEvent = mRecentMotionEvents.get(0);
- mLastMotionEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1);
+ mFirstRecentMotionEvent = recentMotionEvents.get(0);
+ mLastMotionEvent = recentMotionEvents.get(recentMotionEvents.size() - 1);
}
calculateAngleInternal();
@@ -249,6 +274,17 @@ public class FalsingDataProvider {
mDirty = false;
}
+ private boolean shouldDropEvent(MotionEvent event) {
+ if (mRecentMotionEvents.size() < 3) return false;
+
+ MotionEvent lastEvent = mRecentMotionEvents.get(mRecentMotionEvents.size() - 1);
+ boolean isCompletingGesture = event.getActionMasked() == MotionEvent.ACTION_UP
+ && lastEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
+ boolean isRecentEvent =
+ event.getEventTime() - lastEvent.getEventTime() < DROP_EVENT_THRESHOLD_MS;
+ return isCompletingGesture && isRecentEvent;
+ }
+
private void calculateAngleInternal() {
if (mRecentMotionEvents.size() < 2) {
mAngle = Float.MAX_VALUE;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
index 4773f2a3b13e..51aede79b581 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/TimeLimitedMotionEventBuffer.java
@@ -183,7 +183,7 @@ public class TimeLimitedMotionEventBuffer implements List<MotionEvent> {
@Override
public List<MotionEvent> subList(int fromIndex, int toIndex) {
- throw new UnsupportedOperationException();
+ return mMotionEvents.subList(fromIndex, toIndex);
}
class Iter implements ListIterator<MotionEvent> {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index c214f5341450..e049ae09b1de 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -263,10 +263,11 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
@Override // ClipboardListener.ClipboardOverlay
public void setClipData(ClipData data, String source) {
ClipboardModel model = ClipboardModel.fromClipData(mContext, mClipboardUtils, data, source);
- if (mExitAnimator != null && mExitAnimator.isRunning()) {
+ boolean wasExiting = (mExitAnimator != null && mExitAnimator.isRunning());
+ if (wasExiting) {
mExitAnimator.cancel();
}
- boolean shouldAnimate = !model.dataMatches(mClipboardModel);
+ boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
mClipboardModel = model;
mClipboardLogger.setClipSource(mClipboardModel.getSource());
if (shouldAnimate) {
@@ -313,15 +314,19 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
mOnPreviewTapped = this::editText;
break;
case IMAGE:
- if (model.isSensitive() || model.loadThumbnail(mContext) != null) {
- mView.showImagePreview(
- model.isSensitive() ? null : model.loadThumbnail(mContext));
- mView.setEditAccessibilityAction(true);
- mOnPreviewTapped = () -> editImage(model.getUri());
- } else {
- // image loading failed
- mView.showDefaultTextPreview();
- }
+ mBgExecutor.execute(() -> {
+ if (model.isSensitive() || model.loadThumbnail(mContext) != null) {
+ mView.post(() -> {
+ mView.showImagePreview(
+ model.isSensitive() ? null : model.loadThumbnail(mContext));
+ mView.setEditAccessibilityAction(true);
+ });
+ mOnPreviewTapped = () -> editImage(model.getUri());
+ } else {
+ // image loading failed
+ mView.post(mView::showDefaultTextPreview);
+ }
+ });
break;
case URI:
case OTHER:
@@ -346,9 +351,20 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
}
private void animateFromMinimized() {
- mIsMinimized = false;
- setExpandedView();
- animateIn();
+ if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
+ mEnterAnimator.cancel();
+ }
+ mEnterAnimator = mView.getMinimizedFadeoutAnimation();
+ mEnterAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mIsMinimized = false;
+ setExpandedView();
+ animateIn();
+ }
+ });
+ mEnterAnimator.start();
}
private String getAccessibilityAnnouncement(ClipboardModel.Type type) {
@@ -363,15 +379,15 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private void classifyText(ClipboardModel model) {
mBgExecutor.execute(() -> {
- Optional<RemoteAction> remoteAction = mClipboardUtils.getAction(
- model.getText(), model.getTextLinks(), model.getSource());
+ Optional<RemoteAction> remoteAction =
+ mClipboardUtils.getAction(model.getTextLinks(), model.getSource());
if (model.equals(mClipboardModel)) {
remoteAction.ifPresent(action -> {
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
- mView.setActionChip(action, () -> {
+ mView.post(() -> mView.setActionChip(action, () -> {
mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
animateOut();
- });
+ }));
});
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index a85f8b9357f5..25caaeac2c38 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -39,6 +39,9 @@ import javax.inject.Inject;
class ClipboardOverlayUtils {
+ // minimum proportion of entire text an entity must take up, to be considered for smart actions
+ private static final float MINIMUM_ENTITY_PROPORTION = .8f;
+
private final TextClassifier mTextClassifier;
@Inject
@@ -65,19 +68,23 @@ class ClipboardOverlayUtils {
return false;
}
- public Optional<RemoteAction> getAction(CharSequence text, TextLinks textLinks, String source) {
- return getActions(text, textLinks).stream().filter(remoteAction -> {
+ public Optional<RemoteAction> getAction(TextLinks textLinks, String source) {
+ return getActions(textLinks).stream().filter(remoteAction -> {
ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
return component != null && !TextUtils.equals(source, component.getPackageName());
}).findFirst();
}
- private ArrayList<RemoteAction> getActions(CharSequence text, TextLinks textLinks) {
+ private ArrayList<RemoteAction> getActions(TextLinks textLinks) {
ArrayList<RemoteAction> actions = new ArrayList<>();
for (TextLinks.TextLink link : textLinks.getLinks()) {
- TextClassification classification = mTextClassifier.classifyText(
- text, link.getStart(), link.getEnd(), null);
- actions.addAll(classification.getActions());
+ // skip classification for incidental entities
+ if (link.getEnd() - link.getStart()
+ >= textLinks.getText().length() * MINIMUM_ENTITY_PROPORTION) {
+ TextClassification classification = mTextClassifier.classifyText(
+ textLinks.getText(), link.getStart(), link.getEnd(), null);
+ actions.addAll(classification.getActions());
+ }
}
return actions;
}
@@ -92,9 +99,13 @@ class ClipboardOverlayUtils {
private ArrayList<RemoteAction> getActions(ClipData.Item item) {
ArrayList<RemoteAction> actions = new ArrayList<>();
for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
- TextClassification classification = mTextClassifier.classifyText(
- item.getText(), link.getStart(), link.getEnd(), null);
- actions.addAll(classification.getActions());
+ // skip classification for incidental entities
+ if (link.getEnd() - link.getStart()
+ >= item.getText().length() * MINIMUM_ENTITY_PROPORTION) {
+ TextClassification classification = mTextClassifier.classifyText(
+ item.getText(), link.getStart(), link.getEnd(), null);
+ actions.addAll(classification.getActions());
+ }
}
return actions;
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index f372bb4bc7f2..28c57d31a4f3 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -21,6 +21,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
@@ -286,6 +287,20 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
mActionChips.clear();
}
+ Animator getMinimizedFadeoutAnimation() {
+ ObjectAnimator anim = ObjectAnimator.ofFloat(mMinimizedPreview, "alpha", 1, 0);
+ anim.setDuration(66);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mMinimizedPreview.setVisibility(View.GONE);
+ mMinimizedPreview.setAlpha(1);
+ }
+ });
+ return anim;
+ }
+
Animator getEnterAnimation() {
if (mAccessibilityManager.isEnabled()) {
mDismissButton.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
index 24f6296de122..de3a9901b8c4 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsView.java
@@ -45,6 +45,7 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
private SeekBar mSeekbar;
private SeekBarChangeListener mSeekBarListener = new SeekBarChangeListener();
+ private String[] mStateLabels = null;
public SeekBarWithIconButtonsView(Context context) {
this(context, null);
@@ -132,6 +133,30 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
}
/**
+ * Stores the String array we would like to use for describing the state of seekbar progress
+ * and updates the state description with current progress.
+ *
+ * @param labels The state descriptions to be announced for each progress.
+ */
+ public void setProgressStateLabels(String[] labels) {
+ mStateLabels = labels;
+ if (mStateLabels != null) {
+ setSeekbarStateDescription();
+ }
+ }
+
+ /**
+ * Sets the state of seekbar based on current progress. The progress of seekbar is
+ * corresponding to the index of the string array. If the progress is larger than or equals
+ * to the length of the array, the state description is set to an empty string.
+ */
+ private void setSeekbarStateDescription() {
+ mSeekbar.setStateDescription(
+ (mSeekbar.getProgress() < mStateLabels.length)
+ ? mStateLabels[mSeekbar.getProgress()] : "");
+ }
+
+ /**
* Sets a onSeekbarChangeListener to the seekbar in the layout.
* We update the Start Icon and End Icon if needed when the seekbar progress is changed.
*/
@@ -173,6 +198,9 @@ public class SeekBarWithIconButtonsView extends LinearLayout {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (mStateLabels != null) {
+ setSeekbarStateDescription();
+ }
if (mOnSeekBarChangeListener != null) {
mOnSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index a5beb4e85058..3cf26b381d7d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -23,15 +23,16 @@ import com.android.systemui.hdmi.HdmiCecSetMenuLanguageActivity;
import com.android.systemui.keyguard.WorkLockActivity;
import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
-import com.android.systemui.screenshot.AppClipsActivity;
-import com.android.systemui.screenshot.AppClipsTrampolineActivity;
import com.android.systemui.screenshot.LongScreenshotActivity;
+import com.android.systemui.screenshot.appclips.AppClipsActivity;
+import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity;
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
import com.android.systemui.tuner.TunerActivity;
+import com.android.systemui.usb.UsbAccessoryUriActivity;
import com.android.systemui.usb.UsbConfirmActivity;
import com.android.systemui.usb.UsbDebuggingActivity;
import com.android.systemui.usb.UsbDebuggingSecondaryUserActivity;
@@ -97,6 +98,12 @@ public abstract class DefaultActivityBinder {
@ClassKey(UsbConfirmActivity.class)
public abstract Activity bindUsbConfirmActivity(UsbConfirmActivity activity);
+ /** Inject into UsbAccessoryUriActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(UsbAccessoryUriActivity.class)
+ public abstract Activity bindUsbAccessoryUriActivity(UsbAccessoryUriActivity activity);
+
/** Inject into CreateUserActivity. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index ca1cef385755..d0a92f0846d0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -43,7 +43,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launch
@@ -131,9 +130,17 @@ constructor(
}
}
- /** Starts the dream content and dream overlay entry animations. */
+ /**
+ * Starts the dream content and dream overlay entry animations.
+ *
+ * @param downwards if true, the entry animation translations downwards into position rather
+ * than upwards.
+ */
@JvmOverloads
- fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+ fun startEntryAnimations(
+ downwards: Boolean,
+ animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
+ ) {
cancelAnimations()
mAnimator =
@@ -153,7 +160,7 @@ constructor(
interpolator = Interpolators.LINEAR
),
translationYAnimator(
- from = mDreamInTranslationYDistance.toFloat(),
+ from = mDreamInTranslationYDistance.toFloat() * (if (downwards) -1 else 1),
to = 0f,
durationMs = mDreamInTranslationYDurationMs,
interpolator = Interpolators.EMPHASIZED_DECELERATE
@@ -167,6 +174,71 @@ constructor(
}
}
+ /**
+ * Starts the dream content and dream overlay exit animations.
+ *
+ * This should only be used when the low light dream is entering, animations to/from other SysUI
+ * views is controlled by `transitionViewModel`.
+ */
+ // TODO(b/256916668): integrate with the keyguard transition model once dream surfaces work is
+ // done.
+ @JvmOverloads
+ fun startExitAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }): Animator {
+ cancelAnimations()
+
+ mAnimator =
+ animatorBuilder().apply {
+ playTogether(
+ translationYAnimator(
+ from = 0f,
+ to = -mDreamInTranslationYDistance.toFloat(),
+ durationMs = mDreamInTranslationYDurationMs,
+ delayMs = 0,
+ interpolator = Interpolators.EMPHASIZED
+ ),
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_BOTTOM,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_BOTTOM
+ )
+ .apply {
+ doOnEnd {
+ // The logical end of the animation is once the alpha and blur
+ // animations finish, end the animation so that any listeners are
+ // notified. The Y translation animation is much longer than all of
+ // the other animations due to how the spec is defined, but is not
+ // expected to run to completion.
+ mAnimator?.end()
+ }
+ },
+ alphaAnimator(
+ from =
+ mCurrentAlphaAtPosition.getOrDefault(
+ key = POSITION_TOP,
+ defaultValue = 1f
+ ),
+ to = 0f,
+ durationMs = mDreamInComplicationsAnimDurationMs,
+ delayMs = 0,
+ positions = POSITION_TOP
+ )
+ )
+ doOnEnd {
+ mAnimator = null
+ mOverlayStateController.setExitAnimationsRunning(false)
+ }
+ start()
+ }
+ mOverlayStateController.setExitAnimationsRunning(true)
+ return mAnimator as AnimatorSet
+ }
+
/** Starts the dream content and dream overlay exit animations. */
fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
cancelAnimations()
@@ -182,19 +254,6 @@ constructor(
}
}
- /**
- * Ends the dream content and dream overlay animations, if they're currently running.
- *
- * @see [AnimatorSet.end]
- */
- fun endAnimations() {
- mAnimator =
- mAnimator?.let {
- it.end()
- null
- }
- }
-
private fun blurAnimator(
view: View,
fromBlurRadius: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 50cfb6a905c9..4b478cdca9f9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -23,6 +23,7 @@ import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_BOTTOM;
import static com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP;
+import android.animation.Animator;
import android.content.res.Resources;
import android.os.Handler;
import android.util.MathUtils;
@@ -31,6 +32,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
@@ -54,11 +56,14 @@ import javax.inject.Named;
* View controller for {@link DreamOverlayContainerView}.
*/
@DreamOverlayComponent.DreamOverlayScope
-public class DreamOverlayContainerViewController extends ViewController<DreamOverlayContainerView> {
+public class DreamOverlayContainerViewController extends
+ ViewController<DreamOverlayContainerView> implements
+ LowLightTransitionCoordinator.LowLightEnterListener {
private final DreamOverlayStatusBarViewController mStatusBarViewController;
private final BlurUtils mBlurUtils;
private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
private final DreamOverlayStateController mStateController;
+ private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
private final ComplicationHostViewController mComplicationHostViewController;
@@ -143,19 +148,18 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
};
/**
- * If true, overlay entry animations should be skipped once.
- *
- * This is turned on when exiting low light and should be turned off once the entry animations
- * are skipped once.
+ * If {@code true}, the dream has just transitioned from the low light dream back to the user
+ * dream and we should play an entry animation where the overlay slides in downwards from the
+ * top instead of the typicla slide in upwards from the bottom.
*/
- private boolean mSkipEntryAnimations;
+ private boolean mExitingLowLight;
private final DreamOverlayStateController.Callback
mDreamOverlayStateCallback =
new DreamOverlayStateController.Callback() {
@Override
public void onExitLowLight() {
- mSkipEntryAnimations = true;
+ mExitingLowLight = true;
}
};
@@ -165,6 +169,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
ComplicationHostViewController complicationHostViewController,
@Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
DreamOverlayStatusBarViewController statusBarViewController,
+ LowLightTransitionCoordinator lowLightTransitionCoordinator,
BlurUtils blurUtils,
@Main Handler handler,
@Main Resources resources,
@@ -182,6 +187,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mBlurUtils = blurUtils;
mDreamOverlayAnimationsController = animationsController;
mStateController = stateController;
+ mLowLightTransitionCoordinator = lowLightTransitionCoordinator;
mBouncerlessScrimController = bouncerlessScrimController;
mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback);
@@ -208,6 +214,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mStatusBarViewController.init();
mComplicationHostViewController.init();
mDreamOverlayAnimationsController.init(mView);
+ mLowLightTransitionCoordinator.setLowLightEnterListener(this);
}
@Override
@@ -219,14 +226,10 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
// Start dream entry animations. Skip animations for low light clock.
if (!mStateController.isLowLightActive()) {
- mDreamOverlayAnimationsController.startEntryAnimations();
-
- if (mSkipEntryAnimations) {
- // If we're transitioning from the low light dream back to the user dream, skip the
- // overlay animations and show immediately.
- mDreamOverlayAnimationsController.endAnimations();
- mSkipEntryAnimations = false;
- }
+ // If this is transitioning from the low light dream to the user dream, the overlay
+ // should translate in downwards instead of upwards.
+ mDreamOverlayAnimationsController.startEntryAnimations(mExitingLowLight);
+ mExitingLowLight = false;
}
}
@@ -310,4 +313,12 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
}
+
+ @Override
+ public Animator onBeforeEnterLowLight() {
+ // Return the animator so that the transition coordinator waits for the overlay exit
+ // animations to finish before entering low light, as otherwise the default DreamActivity
+ // animation plays immediately and there's no time for this animation to play.
+ return mDreamOverlayAnimationsController.startExitAnimations();
+ }
}
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 a2e11b21ea59..24e90f066622 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -22,14 +22,18 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Debug;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.view.View;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.LifecycleOwner;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.util.ViewController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.Collection;
import java.util.HashMap;
@@ -54,6 +58,8 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
private final LifecycleOwner mLifecycleOwner;
private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
+ @VisibleForTesting
+ boolean mIsAnimationEnabled;
// Whether dream entry animations are finished.
private boolean mEntryAnimationsFinished = false;
@@ -64,7 +70,8 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
ComplicationLayoutEngine layoutEngine,
DreamOverlayStateController dreamOverlayStateController,
LifecycleOwner lifecycleOwner,
- @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
+ @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel,
+ SecureSettings secureSettings) {
super(view);
mLayoutEngine = layoutEngine;
mLifecycleOwner = lifecycleOwner;
@@ -78,6 +85,10 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
mDreamOverlayStateController.areEntryAnimationsFinished();
}
});
+
+ // Whether animations are enabled.
+ mIsAnimationEnabled = secureSettings.getFloatForUser(
+ Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, UserHandle.USER_CURRENT) != 0.0f;
}
@Override
@@ -148,7 +159,7 @@ 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) {
+ if (!mEntryAnimationsFinished && mIsAnimationEnabled) {
view.setVisibility(View.INVISIBLE);
}
mComplications.put(id, viewHolder);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 73c2289ad6bd..a7b3bbcbc37b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -254,7 +254,10 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler {
mCurrentScrimController = mScrimManager.getCurrentController();
session.registerCallback(() -> {
- mVelocityTracker.recycle();
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
mScrimManager.removeCallback(mScrimManagerCallback);
mCapture = null;
mNotificationShadeWindowController.setForcePluginOpen(false, this);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3608b91aa91a..9a6d90a9a88c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -71,8 +71,12 @@ object Flags {
val NOTIFICATION_MEMORY_MONITOR_ENABLED =
releasedFlag(112, "notification_memory_monitor_enabled")
+ /**
+ * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
+ * enable it on release builds.
+ */
val NOTIFICATION_MEMORY_LOGGING_ENABLED =
- unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true)
+ unreleasedFlag(119, "notification_memory_logging_enabled")
// TODO(b/254512731): Tracking Bug
@JvmField val NOTIFICATION_DISMISSAL_FADE = releasedFlag(113, "notification_dismissal_fade")
@@ -104,6 +108,10 @@ object Flags {
val NOTIFICATION_ANIMATE_BIG_PICTURE =
releasedFlag(120, "notification_animate_big_picture", teamfood = true)
+ @JvmField
+ val ANIMATED_NOTIFICATION_SHADE_INSETS =
+ unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
+
// 200 - keyguard/lockscreen
// ** Flag retired **
// public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -438,7 +446,9 @@ object Flags {
)
// TODO(b/256873975): Tracking Bug
- @JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
+ @JvmField
+ @Keep
+ val WM_BUBBLE_BAR = sysPropBooleanFlag(1111, "persist.wm.debug.bubble_bar", default = false)
// TODO(b/260271148): Tracking bug
@Keep
@@ -461,13 +471,13 @@ object Flags {
@Keep
@JvmField
val ENABLE_PIP_SIZE_LARGE_SCREEN =
- sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = false)
+ sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = true)
// TODO(b/265998256): Tracking bug
@Keep
@JvmField
val ENABLE_PIP_APP_ICON_OVERLAY =
- sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = false)
+ sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
// 1200 - predictive back
@Keep
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
index b0f9c4edb073..d078688e5944 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt
@@ -21,6 +21,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.backlight.ui.KeyboardBacklightDialogCoordinator
import javax.inject.Inject
/** A [CoreStartable] that launches components interested in physical keyboard interaction. */
@@ -28,11 +29,12 @@ import javax.inject.Inject
class PhysicalKeyboardCoreStartable
@Inject
constructor(
+ private val keyboardBacklightDialogCoordinator: KeyboardBacklightDialogCoordinator,
private val featureFlags: FeatureFlags,
) : CoreStartable {
override fun start() {
if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) {
- // TODO(b/268645743) start listening for keyboard backlight brightness
+ keyboardBacklightDialogCoordinator.startListening()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt
new file mode 100644
index 000000000000..65e70b319923
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractor.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.keyboard.backlight.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.keyboard.shared.model.BacklightModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+
+/** Allows listening to changes to keyboard backlight level */
+@SysUISingleton
+class KeyboardBacklightInteractor
+@Inject
+constructor(
+ private val keyboardRepository: KeyboardRepository,
+) {
+
+ /** Emits current backlight level as [BacklightModel] or null if keyboard is not connected */
+ val backlight: Flow<BacklightModel?> =
+ keyboardRepository.keyboardConnected.flatMapLatest { connected ->
+ if (connected) keyboardRepository.backlight else flowOf(null)
+ }
+}
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
new file mode 100644
index 000000000000..85d0379a77db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.keyboard.backlight.ui
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.backlight.ui.view.KeyboardBacklightDialog
+import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Based on the state produced from [BacklightDialogViewModel] shows or hides keyboard backlight
+ * indicator
+ */
+@SysUISingleton
+class KeyboardBacklightDialogCoordinator
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val context: Context,
+ private val viewModel: BacklightDialogViewModel,
+) {
+
+ var dialog: KeyboardBacklightDialog? = null
+
+ fun startListening() {
+ applicationScope.launch {
+ viewModel.dialogContent.collect { dialogViewModel ->
+ if (dialogViewModel != null) {
+ if (dialog == null) {
+ dialog = KeyboardBacklightDialog(context, dialogViewModel)
+ // pass viewModel and show
+ }
+ } else {
+ dialog?.dismiss()
+ dialog = null
+ }
+ }
+ }
+ }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index b2489fd57b5d..b68a2a84b5d1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ColorScheme.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -12,19 +12,21 @@
* 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.credentialmanager.common.ui
+package com.android.systemui.keyboard.backlight.ui.view
+
+import android.app.Dialog
+import android.content.Context
+import android.os.Bundle
+import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogContentViewModel
-import androidx.compose.material3.ColorScheme
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.compositeOver
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import kotlin.math.ln
+class KeyboardBacklightDialog(context: Context, val viewModel: BacklightDialogContentViewModel) :
+ Dialog(context) {
-fun ColorScheme.surfaceColorAtElevation(elevation: Dp): Color {
- if (elevation == 0.dp) return surface
- val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f
- return surfaceTint.copy(alpha = alpha).compositeOver(surface)
-} \ No newline at end of file
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ // TODO(b/268650355) Implement the dialog
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogContentViewModel.kt
new file mode 100644
index 000000000000..3ef0ca39b8f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogContentViewModel.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.keyboard.backlight.ui.viewmodel
+
+data class BacklightDialogContentViewModel(val currentValue: Int, val maxValue: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModel.kt
new file mode 100644
index 000000000000..86abca5faaf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModel.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.keyboard.backlight.ui.viewmodel
+
+import android.view.accessibility.AccessibilityManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.backlight.domain.interactor.KeyboardBacklightInteractor
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import javax.inject.Inject
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Responsible for dialog visibility and content - emits [BacklightDialogContentViewModel] if dialog
+ * should be shown and hidden otherwise
+ */
+@SysUISingleton
+class BacklightDialogViewModel
+@Inject
+constructor(
+ interactor: KeyboardBacklightInteractor,
+ private val accessibilityManagerWrapper: AccessibilityManagerWrapper,
+) {
+
+ private val timeoutMillis: Long
+ get() =
+ accessibilityManagerWrapper
+ .getRecommendedTimeoutMillis(
+ DEFAULT_DIALOG_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_ICONS
+ )
+ .toLong()
+
+ val dialogContent: Flow<BacklightDialogContentViewModel?> =
+ interactor.backlight
+ .filterNotNull()
+ .map { BacklightDialogContentViewModel(it.level, it.maxLevel) }
+ .timeout(timeoutMillis, emitAfterTimeout = null)
+
+ private fun <T> Flow<T>.timeout(timeoutMillis: Long, emitAfterTimeout: T): Flow<T> {
+ return flatMapLatest {
+ flow {
+ emit(it)
+ delay(timeoutMillis)
+ emit(emitAfterTimeout)
+ }
+ }
+ }
+
+ private companion object {
+ const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 3000
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
index dd5c5d31fae3..b86083abad21 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt
@@ -25,7 +25,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyboard.data.model.BacklightModel
+import com.android.systemui.keyboard.shared.model.BacklightModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shared/model/BacklightModel.kt
index ea15a9f18584..4a32f79285e3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shared/model/BacklightModel.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.keyboard.data.model
+package com.android.systemui.keyboard.shared.model
/**
* Model for current state of keyboard backlight brightness. [level] indicates current level of
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2ad1ab722d55..377a136920f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -966,13 +966,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];
@@ -982,7 +993,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 =
@@ -1031,6 +1042,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mOccludeByDreamAnimator.start();
});
+ return true;
}
};
@@ -1917,20 +1929,24 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// If the keyguard is already showing, see if we don't need to bother re-showing it. Check
// flags in both files to account for the hiding animation which results in a delay and
- // discrepancy between flags.
+ // discrepancy between flags. If we're in the middle of hiding, do not short circuit so that
+ // we explicitly re-set state.
if (mShowing && mKeyguardStateController.isShowing()) {
- if (mPM.isInteractive()) {
+ if (mPM.isInteractive() && !mHiding) {
// It's already showing, and we're not trying to show it while the screen is off.
// We can simply reset all of the views.
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing (instead, resetting) because it is "
+ + "already showing, we're interactive, and we were not previously hiding. "
+ + "It should be safe to short-circuit here.");
resetStateLocked();
return;
} else {
- // We are trying to show the keyguard while the screen is off - this results from
- // race conditions involving locking while unlocking. Don't short-circuit here and
- // ensure the keyguard is fully re-shown.
+ // We are trying to show the keyguard while the screen is off or while we were in
+ // the middle of hiding - this results from race conditions involving locking while
+ // unlocking. Don't short-circuit here and ensure the keyguard is fully re-shown.
Log.e(TAG,
- "doKeyguard: already showing, but re-showing since we're not interactive");
+ "doKeyguard: already showing, but re-showing because we're interactive or "
+ + "were in the middle of hiding.");
}
}
@@ -2424,11 +2440,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (DEBUG) Log.d(TAG, "handleShow");
}
- mHiding = false;
mKeyguardExitAnimationRunner = null;
mWakeAndUnlocking = false;
setPendingLock(false);
- setShowingLocked(true);
+
+ // Force if we we're showing in the middle of hiding, to ensure we end up in the correct
+ // state.
+ setShowingLocked(true, mHiding /* force */);
+ if (mHiding) {
+ Log.d(TAG, "Forcing setShowingLocked because mHiding=true, which means we're "
+ + "showing in the middle of hiding.");
+ }
+ mHiding = false;
+
mKeyguardViewControllerLazy.get().show(options);
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 47ef0fac17ab..cb891063385f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -46,6 +46,8 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffor
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
@@ -64,6 +66,8 @@ import java.util.concurrent.Executor;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.CoroutineScope;
/**
* Dagger Module providing keyguard.
@@ -153,4 +157,10 @@ public class KeyguardModule {
public ViewMediatorCallback providesViewMediatorCallback(KeyguardViewMediator viewMediator) {
return viewMediator.getViewMediatorCallback();
}
+
+ /** */
+ @Provides
+ public KeyguardQuickAffordancesMetricsLogger providesKeyguardQuickAffordancesMetricsLogger() {
+ return new KeyguardQuickAffordancesMetricsLoggerImpl();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index a3b3d0fd0681..76f20d25b0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -80,6 +80,9 @@ interface KeyguardRepository {
*/
val isKeyguardShowing: Flow<Boolean>
+ /** Is the keyguard in a unlocked state? */
+ val isKeyguardUnlocked: Flow<Boolean>
+
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
@@ -278,6 +281,31 @@ constructor(
}
.distinctUntilChanged()
+ override val isKeyguardUnlocked: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardStateController.Callback {
+ override fun onUnlockedChanged() {
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "updated isKeyguardUnlocked"
+ )
+ }
+ }
+
+ keyguardStateController.addCallback(callback)
+ // Adding the callback does not send an initial update.
+ trySendWithFailureLogging(
+ keyguardStateController.isUnlocked,
+ TAG,
+ "initial isKeyguardUnlocked"
+ )
+
+ awaitClose { keyguardStateController.removeCallback(callback) }
+ }
+ .distinctUntilChanged()
+
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 0c4bca616e12..100bc596103d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -68,8 +68,11 @@ interface KeyguardTransitionRepository {
/**
* Begin a transition from one state to another. Transitions are interruptible, and will issue a
* [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
+ *
+ * When canceled, there are two options: to continue from the current position of the prior
+ * transition, or to reset the position. When [resetIfCanceled] == true, it will do the latter.
*/
- fun startTransition(info: TransitionInfo): UUID?
+ fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean = false): UUID?
/**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -130,7 +133,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
)
}
- override fun startTransition(info: TransitionInfo): UUID? {
+ override fun startTransition(
+ info: TransitionInfo,
+ resetIfCanceled: Boolean,
+ ): UUID? {
if (lastStep.from == info.from && lastStep.to == info.to) {
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return null
@@ -138,7 +144,11 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
val startingValue =
if (lastStep.transitionState != TransitionState.FINISHED) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
- lastStep.value
+ if (resetIfCanceled) {
+ 0f
+ } else {
+ lastStep.value
+ }
} else {
0f
}
@@ -227,10 +237,7 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
}
private fun trace(step: TransitionStep, isManual: Boolean) {
- if (
- step.transitionState != TransitionState.STARTED &&
- step.transitionState != TransitionState.FINISHED
- ) {
+ if (step.transitionState == TransitionState.RUNNING) {
return
}
val traceName =
@@ -243,7 +250,10 @@ class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitio
val traceCookie = traceName.hashCode()
if (step.transitionState == TransitionState.STARTED) {
Trace.beginAsyncSection(traceName, traceCookie)
- } else if (step.transitionState == TransitionState.FINISHED) {
+ } else if (
+ step.transitionState == TransitionState.FINISHED ||
+ step.transitionState == TransitionState.CANCELED
+ ) {
Trace.endAsyncSection(traceName, traceCookie)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 8715d1f55069..3beac0b1322e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -34,7 +34,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -57,14 +56,7 @@ constructor(
private fun listenForDreamingToLockscreen() {
scope.launch {
- // Dependending on the dream, either dream state or occluded change will change first,
- // so listen for both
- combine(keyguardInteractor.isAbleToDream, keyguardInteractor.isKeyguardOccluded) {
- isAbleToDream,
- isKeyguardOccluded ->
- isAbleToDream && isKeyguardOccluded
- }
- .distinctUntilChanged()
+ keyguardInteractor.isAbleToDream
.sample(
combine(
keyguardInteractor.dozeTransitionModel,
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 d01f48970c97..911861ddde47 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
@@ -130,55 +130,59 @@ constructor(
shadeRepository.shadeModel
.sample(
combine(
- keyguardTransitionInteractor.finishedKeyguardState,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep,
keyguardInteractor.statusBarState,
- ::Pair
+ keyguardInteractor.isKeyguardUnlocked,
+ ::toTriple
),
- ::toTriple
+ ::toQuad
)
- .collect { (shadeModel, keyguardState, statusBarState) ->
+ .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) ->
val id = transitionId
if (id != null) {
- // An existing `id` means a transition is started, and calls to
- // `updateTransition` will control it until FINISHED or CANCELED
- var nextState =
- if (shadeModel.expansionAmount == 0f) {
- TransitionState.FINISHED
- } else if (shadeModel.expansionAmount == 1f) {
- TransitionState.CANCELED
- } else {
- TransitionState.RUNNING
- }
- keyguardTransitionRepository.updateTransition(
- id,
- 1f - shadeModel.expansionAmount,
- nextState,
- )
+ if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) {
+ // An existing `id` means a transition is started, and calls to
+ // `updateTransition` will control it until FINISHED or CANCELED
+ var nextState =
+ if (shadeModel.expansionAmount == 0f) {
+ TransitionState.FINISHED
+ } else if (shadeModel.expansionAmount == 1f) {
+ TransitionState.CANCELED
+ } else {
+ TransitionState.RUNNING
+ }
+ keyguardTransitionRepository.updateTransition(
+ id,
+ 1f - shadeModel.expansionAmount,
+ nextState,
+ )
- if (
- nextState == TransitionState.CANCELED ||
- nextState == TransitionState.FINISHED
- ) {
- transitionId = null
- }
+ if (
+ nextState == TransitionState.CANCELED ||
+ nextState == TransitionState.FINISHED
+ ) {
+ transitionId = null
+ }
- // If canceled, just put the state back
- if (nextState == TransitionState.CANCELED) {
- keyguardTransitionRepository.startTransition(
- TransitionInfo(
- ownerName = name,
- from = KeyguardState.PRIMARY_BOUNCER,
- to = KeyguardState.LOCKSCREEN,
- animator = getAnimator(0.milliseconds)
+ // If canceled, just put the state back
+ if (nextState == TransitionState.CANCELED) {
+ keyguardTransitionRepository.startTransition(
+ TransitionInfo(
+ ownerName = name,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ animator = getAnimator(0.milliseconds)
+ )
)
- )
+ }
}
} else {
// TODO (b/251849525): Remove statusbarstate check when that state is
// integrated into KeyguardTransitionRepository
if (
- keyguardState == KeyguardState.LOCKSCREEN &&
+ keyguardState.to == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging &&
+ !isKeyguardUnlocked &&
statusBarState == KEYGUARD
) {
transitionId =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index b59b413d7a40..94961cbf4240 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,6 +17,9 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
+import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -26,6 +29,8 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
@@ -37,7 +42,8 @@ constructor(
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardSecurityModel: KeyguardSecurityModel,
) : TransitionInteractor(FromPrimaryBouncerTransitionInteractor::class.simpleName!!) {
override fun start() {
@@ -93,31 +99,47 @@ constructor(
private fun listenForPrimaryBouncerToGone() {
scope.launch {
keyguardInteractor.isKeyguardGoingAway
- .sample(keyguardTransitionInteractor.finishedKeyguardState) { a, b -> Pair(a, b) }
- .collect { pair ->
- val (isKeyguardGoingAway, keyguardState) = pair
- if (isKeyguardGoingAway && keyguardState == KeyguardState.PRIMARY_BOUNCER) {
+ .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { (isKeyguardGoingAway, lastStartedTransitionStep) ->
+ if (
+ isKeyguardGoingAway &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+ ) {
+ val securityMode =
+ keyguardSecurityModel.getSecurityMode(
+ KeyguardUpdateMonitor.getCurrentUser()
+ )
+ // IME for password requires a slightly faster animation
+ val duration =
+ if (securityMode == Password) {
+ TO_GONE_SHORT_DURATION
+ } else {
+ TO_GONE_DURATION
+ }
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.GONE,
- animator = getAnimator(),
- )
+ animator = getAnimator(duration),
+ ),
+ resetIfCanceled = true,
)
}
}
}
}
- private fun getAnimator(): ValueAnimator {
+ private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
return ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR)
- setDuration(TRANSITION_DURATION_MS)
+ setDuration(duration.inWholeMilliseconds)
}
}
companion object {
- private const val TRANSITION_DURATION_MS = 300L
+ private val DEFAULT_DURATION = 300.milliseconds
+ val TO_GONE_DURATION = 250.milliseconds
+ val TO_GONE_SHORT_DURATION = 200.milliseconds
}
}
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 d25aff0add86..ec99049b42e3 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
@@ -33,7 +33,9 @@ import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDoz
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.keyguard.shared.model.WakefulnessModel.Companion.isWakingOrStartingToWake
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@@ -95,6 +97,9 @@ constructor(
awaitClose { commandQueue.removeCallback(callback) }
}
+ /** The device wake/sleep state */
+ val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
+
/**
* Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
* that doze mode is not running and DREAMING is ok to commence.
@@ -109,6 +114,12 @@ constructor(
isDreaming && isDozeOff(dozeTransitionModel.to)
}
)
+ .sample(
+ wakefulnessModel,
+ { isAbleToDream, wakefulnessModel ->
+ isAbleToDream && isWakingOrStartingToWake(wakefulnessModel)
+ }
+ )
.flatMapLatest { isAbleToDream ->
flow {
delay(50)
@@ -119,6 +130,8 @@ constructor(
/** Whether the keyguard is showing or not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+ /** Whether the keyguard is unlocked or not. */
+ val isKeyguardUnlocked: Flow<Boolean> = repository.isKeyguardUnlocked
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
/** Whether the keyguard is going away. */
@@ -127,8 +140,6 @@ constructor(
val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerVisible
/** Whether the alternate bouncer is showing or not. */
val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
- /** The device wake/sleep state */
- val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
/**
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 bc3c7203ce3d..1735b5dba094 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
@@ -37,6 +37,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardPickerFlag
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
@@ -68,6 +69,7 @@ constructor(
private val featureFlags: FeatureFlags,
private val repository: Lazy<KeyguardQuickAffordanceRepository>,
private val launchAnimator: DialogLaunchAnimator,
+ private val logger: KeyguardQuickAffordancesMetricsLogger,
private val devicePolicyManager: DevicePolicyManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
@@ -122,10 +124,12 @@ constructor(
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
* the affordance that was clicked
* @param expandable An optional [Expandable] for the activity- or dialog-launch animation
+ * @param slotId The id of the lockscreen slot that the affordance is in
*/
fun onQuickAffordanceTriggered(
configKey: String,
expandable: Expandable?,
+ slotId: String,
) {
@Suppress("UNCHECKED_CAST")
val config =
@@ -139,6 +143,7 @@ constructor(
Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
return
}
+ logger.logOnShortcutTriggered(slotId, configKey)
when (val result = config.onTriggered(expandable)) {
is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
@@ -191,6 +196,7 @@ constructor(
affordanceIds = selections,
)
+ logger.logOnShortcutSelected(slotId, affordanceId)
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 51b02779a89f..e650b9fc0e47 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -61,7 +61,15 @@ constructor(
}
scope.launch {
- keyguardInteractor.isDreaming.collect { logger.log(TAG, VERBOSE, "isDreaming", it) }
+ keyguardInteractor.isAbleToDream.collect {
+ logger.log(TAG, VERBOSE, "isAbleToDream", it)
+ }
+ }
+
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded.collect {
+ logger.log(TAG, VERBOSE, "isOccluded", it)
+ }
}
scope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 1b7da5b65a03..3c0ec350c5c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -78,6 +78,10 @@ constructor(
val occludedToLockscreenTransition: Flow<TransitionStep> =
repository.transition(OCCLUDED, LOCKSCREEN)
+ /** PRIMARY_BOUNCER->GONE transition information. */
+ val primaryBouncerToGoneTransition: Flow<TransitionStep> =
+ repository.transition(PRIMARY_BOUNCER, GONE)
+
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
* Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancesMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancesMetricsLogger.kt
new file mode 100644
index 000000000000..0b0225a51412
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancesMetricsLogger.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.quickaffordance
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.SysUiStatsLog
+
+interface KeyguardQuickAffordancesMetricsLogger {
+
+ /**
+ * Logs shortcut Triggered
+ * @param slotId The id of the lockscreen slot that the affordance is in
+ * @param affordanceId The id of the lockscreen affordance
+ */
+ fun logOnShortcutTriggered(slotId: String, affordanceId: String)
+
+ /**
+ * Logs shortcut Selected
+ * @param slotId The id of the lockscreen slot that the affordance is in
+ * @param affordanceId The id of the lockscreen affordance
+ */
+ fun logOnShortcutSelected(slotId: String, affordanceId: String)
+
+}
+
+@SysUISingleton
+class KeyguardQuickAffordancesMetricsLoggerImpl : KeyguardQuickAffordancesMetricsLogger {
+
+ override fun logOnShortcutTriggered(slotId: String, affordanceId: String) {
+ SysUiStatsLog.write(
+ SysUiStatsLog.LOCKSCREEN_SHORTCUT_TRIGGERED,
+ slotId,
+ affordanceId,
+ )
+ }
+
+ override fun logOnShortcutSelected(slotId: String, affordanceId: String) {
+ SysUiStatsLog.write(
+ SysUiStatsLog.LOCKSCREEN_SHORTCUT_SELECTED,
+ slotId,
+ affordanceId,
+ )
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index ca1e27c9d19c..38b9d508f81c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -47,6 +47,7 @@ class KeyguardTransitionAnimationFlow(
duration: Duration,
onStep: (Float) -> Float,
startTime: Duration = 0.milliseconds,
+ onStart: (() -> Unit)? = null,
onCancel: (() -> Float)? = null,
onFinish: (() -> Float)? = null,
interpolator: Interpolator = LINEAR,
@@ -73,6 +74,7 @@ class KeyguardTransitionAnimationFlow(
// the ViewModels of the last update
STARTED -> {
isComplete = false
+ onStart?.invoke()
max(0f, min(1f, value))
}
// Always send a final value of 1. Because of rounding, [value] may never be
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index 2a9060f6db47..d63636c6fccc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -502,6 +502,7 @@ object KeyguardBottomAreaViewBinder {
KeyguardQuickAffordanceViewModel.OnClickedParameters(
configKey = configKey,
expandable = Expandable.fromView(view),
+ slotId = viewModel.slotId,
)
)
}
@@ -568,6 +569,7 @@ object KeyguardBottomAreaViewBinder {
KeyguardQuickAffordanceViewModel.OnClickedParameters(
configKey = viewModel.configKey,
expandable = Expandable.fromView(view),
+ slotId = viewModel.slotId,
)
)
}
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 7db567b2a0e9..2337ffc35fa6 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
@@ -31,6 +31,7 @@ import com.android.settingslib.Utils
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ActivityStarter
import kotlinx.coroutines.awaitCancellation
@@ -44,6 +45,7 @@ object KeyguardBouncerViewBinder {
fun bind(
view: ViewGroup,
viewModel: KeyguardBouncerViewModel,
+ primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
componentFactory: KeyguardBouncerComponent.Factory
) {
// Builds the KeyguardSecurityContainerController from bouncer view group.
@@ -145,6 +147,12 @@ object KeyguardBouncerViewBinder {
}
launch {
+ primaryBouncerToGoneTransitionViewModel.bouncerAlpha.collect { alpha ->
+ securityContainerController.setAlpha(alpha)
+ }
+ }
+
+ launch {
viewModel.bouncerExpansionAmount
.filter { it == EXPANSION_VISIBLE }
.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index ab9e6a4ce045..a8e346477690 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -175,7 +175,8 @@ constructor(
areQuickAffordancesFullyOpaque,
selectedPreviewSlotId,
) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
- val isSelected = selectedPreviewSlotId == position.toSlotId()
+ val slotId = position.toSlotId()
+ val isSelected = selectedPreviewSlotId == slotId
model.toViewModel(
animateReveal = !previewMode.isInPreviewMode && animateReveal,
isClickable = isFullyOpaque && !previewMode.isInPreviewMode,
@@ -187,7 +188,8 @@ constructor(
previewMode.isInPreviewMode &&
previewMode.shouldHighlightSelectedAffordance &&
!isSelected,
- forceInactive = previewMode.isInPreviewMode
+ forceInactive = previewMode.isInPreviewMode,
+ slotId = slotId,
)
}
.distinctUntilChanged()
@@ -200,6 +202,7 @@ constructor(
isSelected: Boolean,
isDimmed: Boolean,
forceInactive: Boolean,
+ slotId: String,
): KeyguardQuickAffordanceViewModel {
return when (this) {
is KeyguardQuickAffordanceModel.Visible ->
@@ -212,6 +215,7 @@ constructor(
quickAffordanceInteractor.onQuickAffordanceTriggered(
configKey = parameters.configKey,
expandable = parameters.expandable,
+ slotId = parameters.slotId,
)
},
isClickable = isClickable,
@@ -219,8 +223,11 @@ constructor(
isSelected = isSelected,
useLongPress = quickAffordanceInteractor.useLongPress,
isDimmed = isDimmed,
+ slotId = slotId,
)
- is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
+ is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel(
+ slotId = slotId,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
index cb68a82118e2..38d1db6d5768 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceViewModel.kt
@@ -32,9 +32,11 @@ data class KeyguardQuickAffordanceViewModel(
val isSelected: Boolean = false,
val useLongPress: Boolean = false,
val isDimmed: Boolean = false,
+ val slotId: String,
) {
data class OnClickedParameters(
val configKey: String,
val expandable: Expandable?,
+ val slotId: String,
)
}
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
new file mode 100644
index 000000000000..92038e24edf3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.ui.viewmodel
+
+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.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
+ * consume.
+ */
+@SysUISingleton
+class PrimaryBouncerToGoneTransitionViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardTransitionInteractor,
+ private val statusBarStateController: SysuiStatusBarStateController,
+) {
+ private val transitionAnimation =
+ KeyguardTransitionAnimationFlow(
+ transitionDuration = TO_GONE_DURATION,
+ transitionFlow = interactor.primaryBouncerToGoneTransition,
+ )
+
+ private var leaveShadeOpen: 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() },
+ onStep = {
+ if (leaveShadeOpen) {
+ 1f
+ } else {
+ 1f - it
+ }
+ },
+ )
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java
index e1e666ef908b..beb725e61e4a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ElevationTokens.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/DeviceStateAutoRotationLog.java
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-package com.android.credentialmanager.common.ui
+package com.android.systemui.log.dagger;
-import androidx.compose.ui.unit.dp
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
-/** Copied from androidx.compose.material3.tokens. */
-internal object ElevationTokens {
- val Level0 = 0.0.dp
- val Level1 = 1.0.dp
- val Level2 = 3.0.dp
- val Level3 = 6.0.dp
- val Level4 = 8.0.dp
- val Level5 = 12.0.dp
-} \ No newline at end of file
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface DeviceStateAutoRotationLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index ca1ed1f5b0be..d246b35ea397 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -370,6 +370,16 @@ public class LogModule {
}
/**
+ * Provides a {@link LogBuffer} for Device State Auto-Rotation logs.
+ */
+ @Provides
+ @SysUISingleton
+ @DeviceStateAutoRotationLog
+ public static LogBuffer provideDeviceStateAutoRotationLogBuffer(LogBufferFactory factory) {
+ return factory.create("DeviceStateAutoRotationLog", 100);
+ }
+
+ /**
* Provides a {@link LogBuffer} for bluetooth-related logs.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
index 680a8b6603d6..67d3be4a3ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselController.kt
@@ -197,7 +197,6 @@ constructor(
private val configListener =
object : ConfigurationController.ConfigurationListener {
- var lastOrientation = -1
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may
@@ -214,13 +213,6 @@ constructor(
override fun onConfigChanged(newConfig: Configuration?) {
if (newConfig == null) return
isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
- val newOrientation = newConfig.orientation
- if (lastOrientation != newOrientation) {
- // The players actually depend on the orientation possibly, so we have to
- // recreate them (at least on large screen devices)
- lastOrientation = newOrientation
- updatePlayers(recreateMedia = true)
- }
}
override fun onUiModeChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 097cc3eef304..a31c1e566018 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -32,12 +32,15 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
import android.graphics.BlendMode;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
@@ -63,7 +66,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.appcompat.content.res.AppCompatResources;
import androidx.constraintlayout.widget.ConstraintSet;
import com.android.internal.annotations.VisibleForTesting;
@@ -146,6 +148,12 @@ public class MediaControlPanel {
private static final int SMARTSPACE_CARD_CLICK_EVENT = 760;
protected static final int SMARTSPACE_CARD_DISMISS_EVENT = 761;
+ private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f;
+ private static final float MEDIA_SCRIM_START_ALPHA = 0.25f;
+ private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f;
+ private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 0.9f;
+ private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f;
+
private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS);
// Buttons to show in small player when using semantic actions
@@ -779,7 +787,7 @@ public class MediaControlPanel {
WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
+ artwork = addGradientToPlayerAlbum(artworkIcon, mutableColorScheme, width, height);
isArtworkBound = true;
} else {
// If there's no artwork, use colors from the app icon
@@ -869,8 +877,9 @@ public class MediaControlPanel {
Trace.beginAsyncSection(traceName, traceCookie);
// Capture width & height from views in foreground for artwork scaling in background
- int width = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredWidth();
- int height = mRecommendationViewHolder.getMediaCoverContainers().get(0).getMeasuredHeight();
+ int width = mContext.getResources().getDimensionPixelSize(R.dimen.qs_media_rec_album_width);
+ int height = mContext.getResources().getDimensionPixelSize(
+ R.dimen.qs_media_rec_album_height_expanded);
mBackgroundExecutor.execute(() -> {
// Album art
@@ -880,7 +889,8 @@ public class MediaControlPanel {
WallpaperColors wallpaperColors = getWallpaperColor(artworkIcon);
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- artwork = addGradientToIcon(artworkIcon, mutableColorScheme, width, height);
+ artwork = addGradientToRecommendationAlbum(artworkIcon, mutableColorScheme, width,
+ height);
} else {
artwork = new ColorDrawable(Color.TRANSPARENT);
}
@@ -889,6 +899,11 @@ public class MediaControlPanel {
// Bind the artwork drawable to media cover.
ImageView mediaCover =
mRecommendationViewHolder.getMediaCoverItems().get(itemIndex);
+ // Rescale media cover
+ Matrix coverMatrix = new Matrix(mediaCover.getImageMatrix());
+ coverMatrix.postScale(REC_MEDIA_COVER_SCALE_FACTOR, REC_MEDIA_COVER_SCALE_FACTOR,
+ 0.5f * width, 0.5f * height);
+ mediaCover.setImageMatrix(coverMatrix);
mediaCover.setImageDrawable(artwork);
// Set up the app icon.
@@ -910,40 +925,62 @@ public class MediaControlPanel {
// This method should be called from a background thread. WallpaperColors.fromBitmap takes a
// good amount of time. We do that work on the background executor to avoid stalling animations
// on the UI Thread.
- private WallpaperColors getWallpaperColor(Icon artworkIcon) {
+ @VisibleForTesting
+ protected WallpaperColors getWallpaperColor(Icon artworkIcon) {
if (artworkIcon != null) {
if (artworkIcon.getType() == Icon.TYPE_BITMAP
|| artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
// Avoids extra processing if this is already a valid bitmap
- return WallpaperColors
- .fromBitmap(artworkIcon.getBitmap());
+ Bitmap artworkBitmap = artworkIcon.getBitmap();
+ if (artworkBitmap.isRecycled()) {
+ Log.d(TAG, "Cannot load wallpaper color from a recycled bitmap");
+ return null;
+ }
+ return WallpaperColors.fromBitmap(artworkBitmap);
} else {
Drawable artworkDrawable = artworkIcon.loadDrawable(mContext);
if (artworkDrawable != null) {
- return WallpaperColors
- .fromDrawable(artworkIcon.loadDrawable(mContext));
+ return WallpaperColors.fromDrawable(artworkDrawable);
}
}
}
return null;
}
- private LayerDrawable addGradientToIcon(
- Icon artworkIcon,
- ColorScheme mutableColorScheme,
- int width,
- int height
- ) {
+ @VisibleForTesting
+ protected LayerDrawable addGradientToPlayerAlbum(Icon artworkIcon,
+ ColorScheme mutableColorScheme, int width, int height) {
Drawable albumArt = getScaledBackground(artworkIcon, width, height);
- GradientDrawable gradient = (GradientDrawable) AppCompatResources
- .getDrawable(mContext, R.drawable.qs_media_scrim);
+ GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
+ R.drawable.qs_media_scrim).mutate();
+ return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
+ MEDIA_SCRIM_START_ALPHA, MEDIA_PLAYER_SCRIM_END_ALPHA);
+ }
+
+ @VisibleForTesting
+ protected LayerDrawable addGradientToRecommendationAlbum(Icon artworkIcon,
+ ColorScheme mutableColorScheme, int width, int height) {
+ // First try scaling rec card using bitmap drawable.
+ // If returns null, set drawable bounds.
+ Drawable albumArt = getScaledRecommendationCover(artworkIcon, width, height);
+ if (albumArt == null) {
+ albumArt = getScaledBackground(artworkIcon, width, height);
+ }
+ GradientDrawable gradient = (GradientDrawable) mContext.getDrawable(
+ R.drawable.qs_media_rec_scrim).mutate();
+ return setupGradientColorOnDrawable(albumArt, gradient, mutableColorScheme,
+ MEDIA_REC_SCRIM_START_ALPHA, MEDIA_REC_SCRIM_END_ALPHA);
+ }
+
+ private LayerDrawable setupGradientColorOnDrawable(Drawable albumArt, GradientDrawable gradient,
+ ColorScheme mutableColorScheme, float startAlpha, float endAlpha) {
gradient.setColors(new int[] {
ColorUtilKt.getColorWithAlpha(
MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
- 0.25f),
+ startAlpha),
ColorUtilKt.getColorWithAlpha(
MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
- 0.9f),
+ endAlpha),
});
return new LayerDrawable(new Drawable[] { albumArt, gradient });
}
@@ -1589,6 +1626,29 @@ public class MediaControlPanel {
}
/**
+ * Scale artwork to fill the background of media covers in recommendation card.
+ */
+ @UiThread
+ private Drawable getScaledRecommendationCover(Icon artworkIcon, int width, int height) {
+ if (width == 0 || height == 0) {
+ return null;
+ }
+ if (artworkIcon != null) {
+ Bitmap bitmap;
+ if (artworkIcon.getType() == Icon.TYPE_BITMAP
+ || artworkIcon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+ Bitmap artworkBitmap = artworkIcon.getBitmap();
+ if (artworkBitmap != null) {
+ bitmap = Bitmap.createScaledBitmap(artworkIcon.getBitmap(), width,
+ height, false);
+ return new BitmapDrawable(mContext.getResources(), bitmap);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
* Get the current media controller
*
* @return the controller
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 0788e6172a78..b4724ddebb9a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -154,9 +154,11 @@ constructor(
return transitionLayout?.translationY ?: 0.0f
}
- /** A callback for RTL config changes */
+ /** A callback for config changes */
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
+ var lastOrientation = -1
+
override fun onConfigChanged(newConfig: Configuration?) {
// Because the TransitionLayout is not always attached (and calculates/caches layout
// results regardless of attach state), we have to force the layoutDirection of the
@@ -169,6 +171,13 @@ constructor(
transitionLayout?.layoutDirection = layoutDirection
refreshState()
}
+ val newOrientation = newConfig.orientation
+ if (lastOrientation != newOrientation) {
+ // Layout dimensions are possibly changing, so we need to update them. (at
+ // least on large screen devices)
+ lastOrientation = newOrientation
+ loadLayoutForType(type)
+ }
}
}
}
@@ -195,13 +204,14 @@ constructor(
* The expanded constraint set used to render a expanded player. If it is modified, make sure to
* call [refreshState]
*/
- val collapsedLayout = ConstraintSet()
-
+ var collapsedLayout = ConstraintSet()
+ @VisibleForTesting set
/**
* The expanded constraint set used to render a collapsed player. If it is modified, make sure
* to call [refreshState]
*/
- val expandedLayout = ConstraintSet()
+ var expandedLayout = ConstraintSet()
+ @VisibleForTesting set
/** Whether the guts are visible for the associated player. */
var isGutsVisible = false
@@ -483,7 +493,7 @@ constructor(
*/
fun attach(transitionLayout: TransitionLayout, type: TYPE) =
traceSection("MediaViewController#attach") {
- updateMediaViewControllerType(type)
+ loadLayoutForType(type)
logger.logMediaLocation("attach $type", currentStartLocation, currentEndLocation)
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
@@ -641,7 +651,7 @@ constructor(
return result
}
- private fun updateMediaViewControllerType(type: TYPE) {
+ private fun loadLayoutForType(type: TYPE) {
this.type = type
// These XML resources contain ConstraintSets that will apply to this player type's layout
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 0dfb9b1f13c7..006cedfe36b4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -1733,41 +1733,26 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT);
}
- if (!mEdgeBackGestureHandler.isHandlingGestures()) {
- // 2/3 button navigation is on. Do not provide any gesture insets here. But need to keep
- // the provider to support runtime update.
- return new InsetsFrameProvider[] {
- navBarProvider,
- new InsetsFrameProvider(
- ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.NONE),
- new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
- Insets.NONE, null),
- new InsetsFrameProvider(ITYPE_RIGHT_GESTURES,
- InsetsFrameProvider.SOURCE_DISPLAY,
- Insets.NONE, null),
- bottomTappableProvider
- };
- } else {
- // Gesture navigation
- final int gestureHeight = userContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.navigation_bar_gesture_height);
- final DisplayCutout cutout = userContext.getDisplay().getCutout();
- final int safeInsetsLeft = cutout != null ? cutout.getSafeInsetLeft() : 0;
- final int safeInsetsRight = cutout != null ? cutout.getSafeInsetRight() : 0;
- return new InsetsFrameProvider[] {
- navBarProvider,
- new InsetsFrameProvider(
- ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.of(0, 0, 0, gestureHeight)),
- new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
- Insets.of(safeInsetsLeft
- + mEdgeBackGestureHandler.getEdgeWidthLeft(), 0, 0, 0), null),
- new InsetsFrameProvider(ITYPE_RIGHT_GESTURES,
- InsetsFrameProvider.SOURCE_DISPLAY,
- Insets.of(0, 0, safeInsetsRight
- + mEdgeBackGestureHandler.getEdgeWidthRight(), 0), null),
- bottomTappableProvider
- };
- }
+ final DisplayCutout cutout = userContext.getDisplay().getCutout();
+ final int safeInsetsLeft = cutout != null ? cutout.getSafeInsetLeft() : 0;
+ final int safeInsetsRight = cutout != null ? cutout.getSafeInsetRight() : 0;
+ final int gestureHeight = userContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
+ final boolean handlingGesture = mEdgeBackGestureHandler.isHandlingGestures();
+ final int gestureInsetsLeft = handlingGesture
+ ? mEdgeBackGestureHandler.getEdgeWidthLeft() + safeInsetsLeft : 0;
+ final int gestureInsetsRight = handlingGesture
+ ? mEdgeBackGestureHandler.getEdgeWidthRight() + safeInsetsRight : 0;
+ return new InsetsFrameProvider[] {
+ navBarProvider,
+ new InsetsFrameProvider(
+ ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.of(0, 0, 0, gestureHeight)),
+ new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.of(gestureInsetsLeft, 0, 0, 0), null),
+ new InsetsFrameProvider(ITYPE_RIGHT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
+ Insets.of(0, 0, gestureInsetsRight, 0), null),
+ bottomTappableProvider
+ };
}
private boolean canShowSecondaryHandle() {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index f335733b430c..70040c75d123 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -2,19 +2,15 @@ package com.android.systemui.navigationbar.gestural
import android.content.Context
import android.content.res.Configuration
-import android.content.res.TypedArray
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Path
import android.graphics.RectF
import android.util.MathUtils.min
-import android.util.TypedValue
import android.view.View
-import androidx.appcompat.view.ContextThemeWrapper
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
-import com.android.internal.R.style.Theme_DeviceDefault
import com.android.internal.util.LatencyTracker
import com.android.settingslib.Utils
import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
@@ -159,26 +155,21 @@ class BackPanel(
val isDeviceInNightTheme = resources.configuration.uiMode and
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
- val colorControlActivated = ContextThemeWrapper(context, Theme_DeviceDefault)
- .run {
- val typedValue = TypedValue()
- val a: TypedArray = obtainStyledAttributes(typedValue.data,
- intArrayOf(android.R.attr.colorControlActivated))
- val color = a.getColor(0, 0)
- a.recycle()
- color
+ arrowPaint.color = Utils.getColorAttrDefaultColor(context,
+ if (isDeviceInNightTheme) {
+ com.android.internal.R.attr.colorAccentPrimary
+ } else {
+ com.android.internal.R.attr.textColorPrimary
}
+ )
- val colorPrimary =
- Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
-
- arrowPaint.color = Utils.getColorAccentDefaultColor(context)
-
- arrowBackgroundPaint.color = if (isDeviceInNightTheme) {
- colorPrimary
- } else {
- colorControlActivated
- }
+ arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context,
+ if (isDeviceInNightTheme) {
+ com.android.internal.R.attr.materialColorOnSecondary
+ } else {
+ com.android.internal.R.attr.colorAccentSecondary
+ }
+ )
}
inner class AnimatedFloat(
@@ -414,9 +405,9 @@ class BackPanel(
) {
horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
scale.updateRestingPosition(restingParams.scale)
- arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha)
backgroundAlpha.updateRestingPosition(restingParams.backgroundDimens.alpha)
+ arrowAlpha.updateRestingPosition(restingParams.arrowDimens.alpha, animate)
arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
scalePivotX.updateRestingPosition(restingParams.backgroundDimens.width, animate)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index f409b23cf4e2..80ed08c901af 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -55,12 +55,12 @@ private const val PX_PER_MS = 1
internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
-private const val MIN_DURATION_COMMITTED_ANIMATION = 200L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L
private const val FAILSAFE_DELAY_MS = 350L
-private const val POP_ON_FLING_DELAY = 160L
+private const val POP_ON_FLING_DELAY = 140L
internal val VIBRATE_ACTIVATED_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
@@ -148,8 +148,6 @@ class BackPanelController internal constructor(
private var gestureSinceActionDown = 0L
private var gestureEntryTime = 0L
private var gestureActiveTime = 0L
- private var gestureInactiveOrEntryTime = 0L
- private var gestureArrowStrokeVisibleTime = 0L
private val elapsedTimeSinceActionDown
get() = SystemClock.uptimeMillis() - gestureSinceActionDown
@@ -441,34 +439,44 @@ class BackPanelController internal constructor(
updateArrowStateOnMove(yTranslation, xTranslation)
- when (currentState) {
- GestureState.ACTIVE -> {
- stretchActiveBackIndicator(fullScreenProgress(xTranslation))
- }
- GestureState.ENTRY -> {
- val progress = staticThresholdProgress(xTranslation)
- stretchEntryBackIndicator(progress)
-
- params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
- mView.popArrowAlpha(0f, it.value)
- }
- }
- GestureState.INACTIVE -> {
- val progress = reactivationThresholdProgress(totalTouchDelta)
- stretchInactiveBackIndicator(progress)
+ val gestureProgress = when (currentState) {
+ GestureState.ACTIVE -> fullScreenProgress(xTranslation)
+ GestureState.ENTRY -> staticThresholdProgress(xTranslation)
+ GestureState.INACTIVE -> reactivationThresholdProgress(totalTouchDelta)
+ else -> null
+ }
- params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
- gestureArrowStrokeVisibleTime = SystemClock.uptimeMillis()
- mView.popArrowAlpha(0f, it.value)
- }
+ gestureProgress?.let {
+ when (currentState) {
+ GestureState.ACTIVE -> stretchActiveBackIndicator(gestureProgress)
+ GestureState.ENTRY -> stretchEntryBackIndicator(gestureProgress)
+ GestureState.INACTIVE -> stretchInactiveBackIndicator(gestureProgress)
+ else -> {}
}
- else -> {}
}
- // set y translation
+ setArrowStrokeAlpha(gestureProgress)
setVerticalTranslation(yOffset)
}
+ private fun setArrowStrokeAlpha(gestureProgress: Float?) {
+ val strokeAlphaProgress = when (currentState) {
+ GestureState.ENTRY -> gestureProgress
+ GestureState.INACTIVE -> gestureProgress
+ GestureState.ACTIVE,
+ GestureState.FLUNG,
+ GestureState.COMMITTED -> 1f
+ GestureState.CANCELLED,
+ GestureState.GONE -> 0f
+ }
+
+ strokeAlphaProgress?.let { progress ->
+ params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let {
+ mView.popArrowAlpha(0f, it.value)
+ }
+ }
+ }
+
private fun setVerticalTranslation(yOffset: Float) {
val yTranslation = abs(yOffset)
val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
@@ -599,7 +607,7 @@ class BackPanelController internal constructor(
private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean {
val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop
- val flingDistance = abs(endX - startX)
+ val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX
val isPastFlingVelocity = isDragAwayFromEdge(
velocityPxPerSecThreshold =
ViewConfiguration.get(context).scaledMinimumFlingVelocity)
@@ -764,7 +772,7 @@ class BackPanelController internal constructor(
GestureState.ENTRY,
GestureState.INACTIVE -> params.entryIndicator.arrowDimens
GestureState.ACTIVE -> params.activeIndicator.arrowDimens
- GestureState.FLUNG,
+ GestureState.FLUNG -> params.flungIndicator.arrowDimens
GestureState.COMMITTED -> params.committedIndicator.arrowDimens
GestureState.CANCELLED -> params.cancelledIndicator.arrowDimens
},
@@ -825,7 +833,6 @@ class BackPanelController internal constructor(
updateRestingArrowDimens()
gestureEntryTime = SystemClock.uptimeMillis()
- gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
}
GestureState.ACTIVE -> {
previousXTranslationOnActiveOffset = previousXTranslation
@@ -857,7 +864,13 @@ class BackPanelController internal constructor(
}
GestureState.INACTIVE -> {
- gestureInactiveOrEntryTime = SystemClock.uptimeMillis()
+
+ // Typically entering INACTIVE means
+ // totalTouchDelta <= deactivationSwipeTriggerThreshold
+ // but because we can also independently enter this state
+ // if touch Y >> touch X, we force it to deactivationSwipeTriggerThreshold
+ // so that gesture progress in this state is consistent regardless of entry
+ totalTouchDelta = params.deactivationSwipeTriggerThreshold
val startingVelocity = convertVelocityToSpringStartingVelocity(
valueOnFastVelocity = -1.05f,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 0c0002221244..d46333a50c8d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -9,8 +9,8 @@ import com.android.systemui.R
data class EdgePanelParams(private var resources: Resources) {
data class ArrowDimens(
- val length: Float = 0f,
- val height: Float = 0f,
+ val length: Float? = 0f,
+ val height: Float? = 0f,
val alpha: Float = 0f,
var alphaSpring: SpringForce? = null,
val heightSpring: SpringForce? = null,
@@ -139,17 +139,17 @@ data class EdgePanelParams(private var resources: Resources) {
entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f)
- activeWidthInterpolator = PathInterpolator(.15f, .48f, .46f, .89f)
+ activeWidthInterpolator = PathInterpolator(.32f, 0f, .16f, .94f)
arrowAngleInterpolator = entryWidthInterpolator
translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f)
farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f)
edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f)
heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f)
- val showArrowOnProgressValue = .2f
+ val showArrowOnProgressValue = .23f
val showArrowOnProgressValueFactor = 1.05f
- val entryActiveHorizontalTranslationSpring = createSpring(675f, 0.8f)
+ val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.8f)
val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f)
val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f)
val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f)
@@ -178,7 +178,7 @@ data class EdgePanelParams(private var resources: Resources) {
height = getDimen(R.dimen.navigation_edge_entry_background_height),
edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners),
- alphaSpring = createSpring(900f, 1f),
+ alphaSpring = createSpring(1100f, 1f),
widthSpring = createSpring(450f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(300f, 0.5f),
@@ -232,7 +232,7 @@ data class EdgePanelParams(private var resources: Resources) {
getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
farCornerRadius =
getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
- widthSpring = createSpring(200f, 0.65f),
+ widthSpring = createSpring(250f, 0.65f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(200f, 1f),
edgeCornerRadiusSpring = createSpring(150f, 0.5f),
@@ -244,6 +244,8 @@ data class EdgePanelParams(private var resources: Resources) {
arrowDimens = activeIndicator.arrowDimens.copy(
lengthSpring = activeCommittedArrowLengthSpring,
heightSpring = activeCommittedArrowHeightSpring,
+ length = null,
+ height = null,
),
backgroundDimens = activeIndicator.backgroundDimens.copy(
alpha = 0f,
@@ -255,13 +257,15 @@ data class EdgePanelParams(private var resources: Resources) {
farCornerRadiusSpring = flungCommittedFarCornerSpring,
),
scale = 0.85f,
- scaleSpring = createSpring(650f, 1f),
+ scaleSpring = createSpring(1150f, 1f),
)
flungIndicator = committedIndicator.copy(
arrowDimens = committedIndicator.arrowDimens.copy(
lengthSpring = createSpring(850f, 0.46f),
heightSpring = createSpring(850f, 0.46f),
+ length = activeIndicator.arrowDimens.length,
+ height = activeIndicator.arrowDimens.height
),
backgroundDimens = committedIndicator.backgroundDimens.copy(
widthSpring = flungCommittedWidthSpring,
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index c65f0aaab91f..5b36e93ec5e1 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -22,6 +22,9 @@ import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
import android.os.Build
import android.os.UserManager
@@ -54,8 +57,8 @@ constructor(
private val resolver: NoteTaskInfoResolver,
private val eventLogger: NoteTaskEventLogger,
private val optionalBubbles: Optional<Bubbles>,
- private val optionalUserManager: Optional<UserManager>,
- private val optionalKeyguardManager: Optional<KeyguardManager>,
+ private val userManager: UserManager,
+ private val keyguardManager: KeyguardManager,
@NoteTaskEnabledKey private val isEnabled: Boolean,
private val devicePolicyManager: DevicePolicyManager,
private val userTracker: UserTracker,
@@ -106,8 +109,6 @@ constructor(
if (!isEnabled) return
val bubbles = optionalBubbles.getOrNull() ?: return
- val userManager = optionalUserManager.getOrNull() ?: return
- val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
@@ -140,12 +141,13 @@ constructor(
logDebug { "onShowNoteTask - start: $info" }
when (info.launchMode) {
is NoteTaskLaunchMode.AppBubble -> {
+ // TODO(b/267634412, b/268351693): Should use `showOrHideAppBubbleAsUser`
bubbles.showOrHideAppBubble(intent)
// App bubble logging happens on `onBubbleExpandChanged`.
logDebug { "onShowNoteTask - opened as app bubble: $info" }
}
is NoteTaskLaunchMode.Activity -> {
- context.startActivity(intent)
+ context.startActivityAsUser(intent, userTracker.userHandle)
eventLogger.logNoteTaskOpened(info)
logDebug { "onShowNoteTask - opened as activity: $info" }
}
@@ -198,12 +200,21 @@ constructor(
}
private fun createNoteIntent(info: NoteTaskInfo): Intent =
- Intent(NoteTaskController.ACTION_CREATE_NOTE)
- .setPackage(info.packageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ Intent(NoteTaskController.ACTION_CREATE_NOTE).apply {
+ setPackage(info.packageName)
+
// EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
// was used to start it.
- .putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+ putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+
+ addFlags(FLAG_ACTIVITY_NEW_TASK)
+ // We should ensure the note experience can be open both as a full screen (lock screen)
+ // and inside the app bubble (contextual). These additional flags will do that.
+ if (info.launchMode == NoteTaskLaunchMode.Activity) {
+ addFlags(FLAG_ACTIVITY_MULTIPLE_TASK)
+ addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ }
+ }
private inline fun logDebug(message: () -> String) {
if (Build.IS_DEBUGGABLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
index acc537a8eb36..2fa8f9a1e6fc 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEntryPoint.kt
@@ -17,7 +17,7 @@ package com.android.systemui.notetask
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceConfig
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
-import com.android.systemui.screenshot.AppClipsTrampolineActivity
+import com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity
/**
* Supported entry points for [NoteTaskController.showNoteTask].
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index 0f75f9591074..7be491f0b3a6 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -17,19 +17,19 @@
package com.android.systemui.notetask
import android.app.role.RoleManager
-import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.os.UserHandle
import android.util.Log
+import com.android.systemui.settings.UserTracker
import javax.inject.Inject
class NoteTaskInfoResolver
@Inject
constructor(
- private val context: Context,
private val roleManager: RoleManager,
private val packageManager: PackageManager,
+ private val userTracker: UserTracker,
) {
fun resolveInfo(
@@ -38,7 +38,7 @@ constructor(
isKeyguardLocked: Boolean = false,
): NoteTaskInfo? {
// TODO(b/267634412): Select UserHandle depending on where the user initiated note-taking.
- val user = context.user
+ val user = userTracker.userHandle
val packageName =
roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index ba8999c068e3..6278c699498c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -17,11 +17,7 @@
package com.android.systemui.notetask
import android.app.Activity
-import android.app.KeyguardManager
import android.app.role.RoleManager
-import android.content.Context
-import android.os.UserManager
-import androidx.core.content.getSystemService
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
@@ -32,7 +28,6 @@ import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
-import java.util.Optional
/** Compose all dependencies required by Note Task feature. */
@Module(includes = [NoteTaskQuickAffordanceModule::class])
@@ -55,15 +50,5 @@ interface NoteTaskModule {
val isFeatureEnabled = featureFlags.isEnabled(Flags.NOTE_TASKS)
return isRoleAvailable && isFeatureEnabled
}
-
- @Provides
- fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
- return Optional.ofNullable(context.getSystemService())
- }
-
- @Provides
- fun provideOptionalUserManager(context: Context): Optional<UserManager> {
- return Optional.ofNullable(context.getSystemService())
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
index 8ced46461dbb..5c59532e0c2e 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -17,8 +17,10 @@
package com.android.systemui.notetask.shortcut
import android.app.Activity
+import android.app.role.RoleManager
import android.content.Intent
import android.os.Bundle
+import android.os.PersistableBundle
import androidx.activity.ComponentActivity
import androidx.annotation.DrawableRes
import androidx.core.content.pm.ShortcutInfoCompat
@@ -36,7 +38,11 @@ import javax.inject.Inject
* href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
* a custom shortcut activity</a>
*/
-class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+class CreateNoteTaskShortcutActivity
+@Inject
+constructor(
+ private val roleManager: RoleManager,
+) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -59,12 +65,19 @@ class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity()
intent: Intent,
@DrawableRes iconResource: Int,
): Intent {
+ val extras = PersistableBundle()
+
+ roleManager.getRoleHoldersAsUser(RoleManager.ROLE_NOTES, user).firstOrNull()?.let { name ->
+ extras.putString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE, name)
+ }
+
val shortcutInfo =
ShortcutInfoCompat.Builder(this, id)
.setIntent(intent)
.setShortLabel(shortLabel)
.setLongLived(true)
.setIcon(IconCompat.createWithResource(this, iconResource))
+ .setExtras(extras)
.build()
return ShortcutManagerCompat.createShortcutResultIntent(
@@ -75,5 +88,16 @@ class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity()
private companion object {
private const val SHORTCUT_ID = "note-task-shortcut-id"
+
+ /**
+ * Shortcut extra which can point to a package name and can be used to indicate an alternate
+ * badge info. Launcher only reads this if the shortcut comes from a system app.
+ *
+ * Duplicated from [com.android.launcher3.icons.IconCache].
+ *
+ * @see com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE
+ */
+ private const val EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE =
+ "extra_shortcut_badge_override_package"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 7c2536dac56e..d4854e1a7daf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -328,7 +328,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
if (listening) {
updateDefaultTileAndIcon();
refreshState();
- if (!mServiceManager.isActiveTile()) {
+ if (!mServiceManager.isActiveTile() || !isTileReady()) {
mServiceManager.setBindRequested(true);
mService.onStartListening();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 25ff308b46bb..019ca52107dd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -631,7 +631,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
final NavigationBarView navBarView =
mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
final NotificationPanelViewController panelController =
- mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController();
+ mCentralSurfacesOptionalLazy.get()
+ .map(CentralSurfaces::getNotificationPanelViewController)
+ .orElse(null);
if (SysUiState.DEBUG) {
Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
+ " navBarView=" + navBarView + " panelController=" + panelController);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
index ead3b7b1de53..0b4b7c691cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DraggableConstraintLayout.java
@@ -45,6 +45,7 @@ public class DraggableConstraintLayout extends ConstraintLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
private static final float VELOCITY_DP_PER_MS = 1;
+ private static final int MAXIMUM_DISMISS_DISTANCE_DP = 400;
private final SwipeDismissHandler mSwipeDismissHandler;
private final GestureDetector mSwipeDetector;
@@ -347,14 +348,18 @@ public class DraggableConstraintLayout extends ConstraintLayout
} else {
finalX = -1 * getBackgroundRight();
}
- float distance = Math.abs(finalX - startX);
+ float distance = Math.min(Math.abs(finalX - startX),
+ FloatingWindowUtil.dpToPx(mDisplayMetrics, MAXIMUM_DISMISS_DISTANCE_DP));
+ // ensure that view dismisses in the right direction (right in LTR, left in RTL)
+ float distanceVector = Math.copySign(distance, finalX - startX);
anim.addUpdateListener(animation -> {
- float translation = MathUtils.lerp(startX, finalX, animation.getAnimatedFraction());
+ float translation = MathUtils.lerp(
+ startX, startX + distanceVector, animation.getAnimatedFraction());
mView.setTranslationX(translation);
mView.setAlpha(1 - animation.getAnimatedFraction());
});
- anim.setDuration((long) (distance / Math.abs(velocity)));
+ anim.setDuration((long) (Math.abs(distance / velocity)));
return anim;
}
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/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/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index 3133924339f2..4756cc8172e9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.appclips;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
-import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
-import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
+import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
+import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.PERMISSION_SELF;
import android.app.Activity;
import android.content.BroadcastReceiver;
@@ -52,6 +52,8 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLogger.UiEventEnum;
import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.screenshot.CropView;
+import com.android.systemui.screenshot.MagnifierView;
import com.android.systemui.settings.UserTracker;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
index 65fb4c9bfb0d..e1619dc9b6ee 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -32,12 +32,12 @@ import javax.inject.Inject;
/** An intermediary singleton object to help communicating with the cross process service. */
@SysUISingleton
-public class AppClipsCrossProcessHelper {
+class AppClipsCrossProcessHelper {
private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector;
@Inject
- public AppClipsCrossProcessHelper(@Application Context context) {
+ AppClipsCrossProcessHelper(@Application Context context) {
mProxyConnector = new ServiceConnector.Impl<IAppClipsScreenshotHelperService>(context,
new Intent(context, AppClipsScreenshotHelperService.class),
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
@@ -52,7 +52,7 @@ public class AppClipsCrossProcessHelper {
* pass around but not a {@link Bitmap}.
*/
@Nullable
- public Bitmap takeScreenshot() {
+ Bitmap takeScreenshot() {
try {
AndroidFuture<ScreenshotHardwareBufferInternal> future =
mProxyConnector.postForResult(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsEvent.java
new file mode 100644
index 000000000000..7a085b9fd7d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsEvent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.screenshot.appclips;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+enum AppClipsEvent implements UiEventLogger.UiEventEnum {
+
+ @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);
+
+ private final int mId;
+
+ AppClipsEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
index 6f8c36595c74..83ff020362f1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
@@ -24,7 +24,6 @@ import android.window.ScreenCapture.ScreenshotSync;
import androidx.annotation.Nullable;
-import com.android.systemui.screenshot.AppClipsActivity;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index eda38e45c98a..3cb1a34a921c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.appclips;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
@@ -24,7 +24,7 @@ import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
-import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
+import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
import android.app.Activity;
import android.app.admin.DevicePolicyManager;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index b2910fd48854..4cbca28a4032 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.appclips;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
-import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
-
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.HardwareRenderer;
@@ -31,7 +29,6 @@ import android.net.Uri;
import android.os.Process;
import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
@@ -39,11 +36,10 @@ import androidx.lifecycle.ViewModelProvider;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.screenshot.appclips.AppClipsCrossProcessHelper;
+import com.android.systemui.screenshot.ImageExporter;
import com.google.common.util.concurrent.ListenableFuture;
-import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
@@ -52,8 +48,7 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
/** A {@link ViewModel} to help with the App Clips screenshot flow. */
-@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
-public final class AppClipsViewModel extends ViewModel {
+final class AppClipsViewModel extends ViewModel {
private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
private final ImageExporter mImageExporter;
@@ -80,8 +75,7 @@ public final class AppClipsViewModel extends ViewModel {
}
/** Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link LiveData}. */
- @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
- public void performScreenshot() {
+ void performScreenshot() {
mBgExecutor.execute(() -> {
Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot();
mMainExecutor.execute(() -> {
@@ -95,14 +89,12 @@ public final class AppClipsViewModel extends ViewModel {
}
/** Returns a {@link LiveData} that holds the captured screenshot. */
- @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
- public LiveData<Bitmap> getScreenshot() {
+ LiveData<Bitmap> getScreenshot() {
return mScreenshotLiveData;
}
/** Returns a {@link LiveData} that holds the {@link Uri} where screenshot is saved. */
- @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
- public LiveData<Uri> getResultLiveData() {
+ LiveData<Uri> getResultLiveData() {
return mResultLiveData;
}
@@ -110,8 +102,7 @@ public final class AppClipsViewModel extends ViewModel {
* Returns a {@link LiveData} that holds the error codes for
* {@link Intent#EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE}.
*/
- @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
- public LiveData<Integer> getErrorLiveData() {
+ LiveData<Integer> getErrorLiveData() {
return mErrorLiveData;
}
@@ -119,8 +110,7 @@ public final class AppClipsViewModel extends ViewModel {
* Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
* {@link LiveData}.
*/
- @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
- public void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
+ void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
mBgExecutor.execute(() -> {
// Render the screenshot bitmap in background.
Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);
@@ -128,7 +118,7 @@ public final class AppClipsViewModel extends ViewModel {
// Export and save the screenshot in background.
// TODO(b/267310185): Save to work profile UserHandle.
ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
- mBgExecutor, UUID.randomUUID(), screenshotBitmap, ZonedDateTime.now(),
+ mBgExecutor, UUID.randomUUID(), screenshotBitmap,
Process.myUserHandle());
// Get the result and update state on main thread.
@@ -160,8 +150,7 @@ public final class AppClipsViewModel extends ViewModel {
}
/** Helper factory to help with injecting {@link AppClipsViewModel}. */
- @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
- public static final class Factory implements ViewModelProvider.Factory {
+ static final class Factory implements ViewModelProvider.Factory {
private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
private final ImageExporter mImageExporter;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java
index 3b107f101088..1e53ebb7935e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java
@@ -28,7 +28,7 @@ import android.window.ScreenCapture.ScreenshotHardwareBuffer;
* An internal version of {@link ScreenshotHardwareBuffer} that helps with parceling the information
* necessary for creating a {@link Bitmap}.
*/
-public class ScreenshotHardwareBufferInternal implements Parcelable {
+class ScreenshotHardwareBufferInternal implements Parcelable {
public static final Creator<ScreenshotHardwareBufferInternal> CREATOR =
new Creator<>() {
@@ -45,7 +45,7 @@ public class ScreenshotHardwareBufferInternal implements Parcelable {
private final HardwareBuffer mHardwareBuffer;
private final ParcelableColorSpace mParcelableColorSpace;
- public ScreenshotHardwareBufferInternal(
+ ScreenshotHardwareBufferInternal(
ScreenshotHardwareBuffer screenshotHardwareBuffer) {
mHardwareBuffer = screenshotHardwareBuffer.getHardwareBuffer();
mParcelableColorSpace = new ParcelableColorSpace(
@@ -65,7 +65,7 @@ public class ScreenshotHardwareBufferInternal implements Parcelable {
* {@link Bitmap#wrapHardwareBuffer(HardwareBuffer, ColorSpace)} and
* {@link HardwareBuffer#close()} for more information.
*/
- public Bitmap createBitmapThenCloseBuffer() {
+ Bitmap createBitmapThenCloseBuffer() {
Bitmap bitmap = Bitmap.wrapHardwareBuffer(mHardwareBuffer,
mParcelableColorSpace.getColorSpace());
mHardwareBuffer.close();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b1987c151e5f..ee9e54a22ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -4720,8 +4720,13 @@ public final class NotificationPanelViewController implements Dumpable {
gesture possible. */
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
+ if (mTrackingPointer < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ } else {
+ mShadeLog.logMotionEvent(event, "Skipping intercept of multitouch pointer");
+ return false;
+ }
}
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 87350b465895..c130b3913b64 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationInsetsController;
@@ -133,7 +134,8 @@ public class NotificationShadeWindowViewController {
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory,
AlternateBouncerInteractor alternateBouncerInteractor,
- KeyguardTransitionInteractor keyguardTransitionInteractor
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel
) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
@@ -160,6 +162,7 @@ public class NotificationShadeWindowViewController {
KeyguardBouncerViewBinder.bind(
mView.findViewById(R.id.keyguard_bouncer_container),
keyguardBouncerViewModel,
+ primaryBouncerToGoneTransitionViewModel,
keyguardBouncerComponentFactory);
collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index c35c5c522798..77550038c94a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -93,6 +93,16 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
@IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
public @interface VisibleState { }
+ /** Returns a human-readable string of {@link VisibleState}. */
+ public static String getVisibleStateString(@VisibleState int state) {
+ switch(state) {
+ case STATE_ICON: return "ICON";
+ case STATE_DOT: return "DOT";
+ case STATE_HIDDEN: return "HIDDEN";
+ default: return "UNKNOWN";
+ }
+ }
+
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -561,7 +571,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
@Override
public String toString() {
return "StatusBarIconView("
- + "slot='" + mSlot + " alpha=" + getAlpha() + " icon=" + mIcon
+ + "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon
+ + " visibleState=" + getVisibleStateString(getVisibleState())
+ " iconColor=#" + Integer.toHexString(mIconColor)
+ " notification=" + mNotification + ')';
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
new file mode 100644
index 000000000000..5ce1db2b6acd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinator.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.notification.collection.coordinator
+
+import android.util.ArrayMap
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.math.max
+import kotlin.math.min
+
+/** A small coordinator which finds, stores, and applies the closest notification time. */
+@CoordinatorScope
+class GroupWhenCoordinator
+@Inject
+constructor(
+ @Main private val delayableExecutor: DelayableExecutor,
+ private val systemClock: SystemClock
+) : Coordinator {
+
+ private val invalidator = object : Invalidator("GroupWhenCoordinator") {}
+ private val notificationGroupTimes = ArrayMap<GroupEntry, Long>()
+ private var cancelInvalidateListRunnable: Runnable? = null
+
+ private val invalidateListRunnable: Runnable = Runnable {
+ invalidator.invalidateList("future notification invalidation")
+ }
+
+ override fun attach(pipeline: NotifPipeline) {
+ pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilterListener)
+ pipeline.addOnAfterRenderGroupListener(::onAfterRenderGroupListener)
+ pipeline.addPreRenderInvalidator(invalidator)
+ }
+
+ private fun onBeforeFinalizeFilterListener(entries: List<ListEntry>) {
+ cancelListInvalidation()
+ notificationGroupTimes.clear()
+
+ val now = systemClock.currentTimeMillis()
+ var closestFutureTime = Long.MAX_VALUE
+ entries.asSequence().filterIsInstance<GroupEntry>().forEach { groupEntry ->
+ val whenMillis = calculateGroupNotificationTime(groupEntry, now)
+ notificationGroupTimes[groupEntry] = whenMillis
+ if (whenMillis > now) {
+ closestFutureTime = min(closestFutureTime, whenMillis)
+ }
+ }
+
+ if (closestFutureTime != Long.MAX_VALUE) {
+ cancelInvalidateListRunnable =
+ delayableExecutor.executeDelayed(invalidateListRunnable, closestFutureTime - now)
+ }
+ }
+
+ private fun cancelListInvalidation() {
+ cancelInvalidateListRunnable?.run()
+ cancelInvalidateListRunnable = null
+ }
+
+ private fun onAfterRenderGroupListener(group: GroupEntry, controller: NotifGroupController) {
+ notificationGroupTimes[group]?.let(controller::setNotificationGroupWhen)
+ }
+
+ private fun calculateGroupNotificationTime(
+ groupEntry: GroupEntry,
+ currentTimeMillis: Long
+ ): Long {
+ var pastTime = Long.MIN_VALUE
+ var futureTime = Long.MAX_VALUE
+ groupEntry.children
+ .asSequence()
+ .mapNotNull { child -> child.sbn.notification.`when`.takeIf { it > 0 } }
+ .forEach { time ->
+ val isInThePast = currentTimeMillis - time > 0
+ if (isInThePast) {
+ pastTime = max(pastTime, time)
+ } else {
+ futureTime = min(futureTime, time)
+ }
+ }
+
+ if (pastTime == Long.MIN_VALUE && futureTime == Long.MAX_VALUE) {
+ return checkNotNull(groupEntry.summary).creationTime
+ }
+
+ return if (futureTime != Long.MAX_VALUE) futureTime else pastTime
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 8a82bcad44e4..6bb5b9218ed7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -31,31 +31,32 @@ interface NotifCoordinators : Coordinator, PipelineDumpable
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
- notifPipelineFlags: NotifPipelineFlags,
- dataStoreCoordinator: DataStoreCoordinator,
- hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
- hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
- keyguardCoordinator: KeyguardCoordinator,
- rankingCoordinator: RankingCoordinator,
- appOpsCoordinator: AppOpsCoordinator,
- deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
- bubbleCoordinator: BubbleCoordinator,
- headsUpCoordinator: HeadsUpCoordinator,
- gutsCoordinator: GutsCoordinator,
- conversationCoordinator: ConversationCoordinator,
- debugModeCoordinator: DebugModeCoordinator,
- groupCountCoordinator: GroupCountCoordinator,
- mediaCoordinator: MediaCoordinator,
- preparationCoordinator: PreparationCoordinator,
- remoteInputCoordinator: RemoteInputCoordinator,
- rowAppearanceCoordinator: RowAppearanceCoordinator,
- stackCoordinator: StackCoordinator,
- shadeEventCoordinator: ShadeEventCoordinator,
- smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
- viewConfigCoordinator: ViewConfigCoordinator,
- visualStabilityCoordinator: VisualStabilityCoordinator,
- sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator
+ notifPipelineFlags: NotifPipelineFlags,
+ dataStoreCoordinator: DataStoreCoordinator,
+ hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+ hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+ keyguardCoordinator: KeyguardCoordinator,
+ rankingCoordinator: RankingCoordinator,
+ appOpsCoordinator: AppOpsCoordinator,
+ deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+ bubbleCoordinator: BubbleCoordinator,
+ headsUpCoordinator: HeadsUpCoordinator,
+ gutsCoordinator: GutsCoordinator,
+ conversationCoordinator: ConversationCoordinator,
+ debugModeCoordinator: DebugModeCoordinator,
+ groupCountCoordinator: GroupCountCoordinator,
+ groupWhenCoordinator: GroupWhenCoordinator,
+ mediaCoordinator: MediaCoordinator,
+ preparationCoordinator: PreparationCoordinator,
+ remoteInputCoordinator: RemoteInputCoordinator,
+ rowAppearanceCoordinator: RowAppearanceCoordinator,
+ stackCoordinator: StackCoordinator,
+ shadeEventCoordinator: ShadeEventCoordinator,
+ smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+ viewConfigCoordinator: ViewConfigCoordinator,
+ visualStabilityCoordinator: VisualStabilityCoordinator,
+ sensitiveContentCoordinator: SensitiveContentCoordinator,
+ dismissibilityCoordinator: DismissibilityCoordinator
) : NotifCoordinators {
private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -82,6 +83,7 @@ class NotifCoordinatorsImpl @Inject constructor(
mCoordinators.add(debugModeCoordinator)
mCoordinators.add(conversationCoordinator)
mCoordinators.add(groupCountCoordinator)
+ mCoordinators.add(groupWhenCoordinator)
mCoordinators.add(mediaCoordinator)
mCoordinators.add(rowAppearanceCoordinator)
mCoordinators.add(stackCoordinator)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
index e2edc01f0d7c..061ef9e9341c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt
@@ -20,4 +20,7 @@ package com.android.systemui.statusbar.notification.collection.render
interface NotifGroupController {
/** Set the number of children that this group would have if not for the 8-child max */
fun setUntruncatedChildCount(untruncatedChildCount: Int)
+
+ /** Set the when value of notification group that reflects most important closest notification time */
+ fun setNotificationGroupWhen(whenMillis: Long)
} \ No newline at end of file
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 2affa77eee04..6deaa23ca20e 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
@@ -192,7 +192,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mMaxSmallHeightBeforeS;
private int mMaxSmallHeight;
private int mMaxSmallHeightLarge;
- private int mMaxSmallHeightMedia;
private int mMaxExpandedHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
@@ -853,6 +852,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
+ * @see NotificationChildrenContainer#setNotificationGroupWhen(long)
+ */
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setNotificationGroupWhen(whenMillis);
+ } else {
+ Log.w(TAG, "setNotificationGroupWhen( whenMillis: " + whenMillis + ")"
+ + " mIsSummaryWithChildren: false"
+ + " mChildrenContainer has not been inflated yet.");
+ }
+ }
+
+ /**
* Called after children have been attached to set the expansion states
*/
public void resetChildSystemExpandedStates() {
@@ -1774,8 +1786,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
R.dimen.notification_min_height);
mMaxSmallHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
- mMaxSmallHeightMedia = NotificationUtils.getFontScaledHeight(mContext,
- R.dimen.notification_min_height_media);
mMaxExpandedHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 2dda6fd802e8..dfc80fde3cd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -349,6 +349,15 @@ public class ExpandableNotificationRowController implements NotifViewController
}
@Override
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mView.isSummaryWithChildren()) {
+ mView.setNotificationGroupWhen(whenMillis);
+ } else {
+ Log.w(TAG, "Called setNotificationTime(" + whenMillis + ") on a leaf row");
+ }
+ }
+
+ @Override
public void setSystemExpanded(boolean systemExpanded) {
mView.setSystemExpanded(systemExpanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 1f664cb16179..9a777ea6230b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -27,6 +27,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.widget.DateTimeView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -344,6 +345,21 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
mTransformationHelper.setVisible(visible);
}
+ /***
+ * Set Notification when value
+ * @param whenMillis
+ */
+ public void setNotificationWhen(long whenMillis) {
+ if (mNotificationHeader == null) {
+ return;
+ }
+
+ final View timeView = mNotificationHeader.findViewById(com.android.internal.R.id.time);
+
+ if (timeView instanceof DateTimeView) {
+ ((DateTimeView) timeView).setTime(whenMillis);
+ }
+ }
protected void addTransformedViews(View... views) {
for (View view : views) {
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 9b93d7b9e1d0..40f55bd3726c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -296,6 +296,19 @@ public class NotificationChildrenContainer extends ViewGroup
}
/**
+ * Set the notification time in the group so that the view can show the latest event in the UI
+ * appropriately.
+ */
+ public void setNotificationGroupWhen(long whenMillis) {
+ if (mNotificationHeaderWrapper != null) {
+ mNotificationHeaderWrapper.setNotificationWhen(whenMillis);
+ }
+ if (mNotificationHeaderWrapperLowPriority != null) {
+ mNotificationHeaderWrapperLowPriority.setNotificationWhen(whenMillis);
+ }
+ }
+
+ /**
* Add a child notification to this view.
*
* @param row the row to add
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 e09b94bb661d..8d782e1c9fa0 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
@@ -67,6 +67,7 @@ import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
@@ -199,6 +200,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private final boolean mDebugRemoveAnimation;
private final boolean mSimplifiedAppearFraction;
private final boolean mUseRoundnessSourceTypes;
+ private boolean mAnimatedInsets;
private int mContentHeight;
private float mIntrinsicContentHeight;
@@ -207,7 +209,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private int mTopPadding;
private boolean mAnimateNextTopPaddingChange;
private int mBottomPadding;
- private int mBottomInset = 0;
+ @VisibleForTesting
+ int mBottomInset = 0;
private float mQsExpansionFraction;
private final int mSplitShadeMinContentHeight;
@@ -388,9 +391,33 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
};
+
private boolean mPulsing;
private boolean mScrollable;
private View mForcedScroll;
+ private boolean mIsInsetAnimationRunning;
+
+ private final WindowInsetsAnimation.Callback mInsetsCallback =
+ new WindowInsetsAnimation.Callback(
+ WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
+
+ @Override
+ public void onPrepare(WindowInsetsAnimation animation) {
+ mIsInsetAnimationRunning = true;
+ }
+
+ @Override
+ public WindowInsets onProgress(WindowInsets windowInsets,
+ List<WindowInsetsAnimation> list) {
+ updateBottomInset(windowInsets);
+ return windowInsets;
+ }
+
+ @Override
+ public void onEnd(WindowInsetsAnimation animation) {
+ mIsInsetAnimationRunning = false;
+ }
+ };
/**
* @see #setHideAmount(float, float)
@@ -584,6 +611,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
+ setAnimatedInsetsEnabled(featureFlags.isEnabled(Flags.ANIMATED_NOTIFICATION_SHADE_INSETS));
mSectionsManager = Dependency.get(NotificationSectionsManager.class);
mScreenOffAnimationController =
Dependency.get(ScreenOffAnimationController.class);
@@ -622,6 +650,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ if (mAnimatedInsets) {
+ setWindowInsetsAnimationCallback(mInsetsCallback);
+ }
}
/**
@@ -690,6 +721,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
@VisibleForTesting
+ void setAnimatedInsetsEnabled(boolean enabled) {
+ mAnimatedInsets = enabled;
+ }
+
+ @VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
if (mFooterView == null) {
@@ -1781,7 +1817,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return;
}
mForcedScroll = v;
- scrollTo(v);
+ if (mAnimatedInsets) {
+ updateForcedScroll();
+ } else {
+ scrollTo(v);
+ }
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -1813,26 +1853,46 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
+ ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
}
+ private void updateBottomInset(WindowInsets windowInsets) {
+ mBottomInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
+
+ if (mForcedScroll != null) {
+ updateForcedScroll();
+ }
+
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ setOwnScrollY(range);
+ }
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
+ if (!mAnimatedInsets) {
+ mBottomInset = insets.getInsets(WindowInsets.Type.ime()).bottom;
+ }
mWaterfallTopInset = 0;
final DisplayCutout cutout = insets.getDisplayCutout();
if (cutout != null) {
mWaterfallTopInset = cutout.getWaterfallInsets().top;
}
-
- int range = getScrollRange();
- if (mOwnScrollY > range) {
- // HACK: We're repeatedly getting staggered insets here while the IME is
- // animating away. To work around that we'll wait until things have settled.
- removeCallbacks(mReclamp);
- postDelayed(mReclamp, 50);
- } else if (mForcedScroll != null) {
- // The scroll was requested before we got the actual inset - in case we need
- // to scroll up some more do so now.
- scrollTo(mForcedScroll);
+ if (mAnimatedInsets && !mIsInsetAnimationRunning) {
+ // update bottom inset e.g. after rotation
+ updateBottomInset(insets);
+ }
+ if (!mAnimatedInsets) {
+ int range = getScrollRange();
+ if (mOwnScrollY > range) {
+ // HACK: We're repeatedly getting staggered insets here while the IME is
+ // animating away. To work around that we'll wait until things have settled.
+ removeCallbacks(mReclamp);
+ postDelayed(mReclamp, 50);
+ } else if (mForcedScroll != null) {
+ // The scroll was requested before we got the actual inset - in case we need
+ // to scroll up some more do so now.
+ scrollTo(mForcedScroll);
+ }
}
return insets;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 7855cdfeb4c3..cf5ecdddf854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -389,6 +389,9 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
boolean isStrongBiometric) {
Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
if (mUpdateMonitor.isGoingToSleep()) {
+ mLogger.deferringAuthenticationDueToSleep(userId,
+ biometricSourceType,
+ mPendingAuthenticated != null);
mPendingAuthenticated = new PendingAuthenticated(userId, biometricSourceType,
isStrongBiometric);
Trace.endSection();
@@ -795,6 +798,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
public void onFinishedGoingToSleep() {
Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
if (mPendingAuthenticated != null) {
+ mLogger.finishedGoingToSleepWithPendingAuth();
PendingAuthenticated pendingAuthenticated = mPendingAuthenticated;
// Post this to make sure it's executed after the device is fully locked.
mHandler.post(() -> onBiometricAuthenticated(pendingAuthenticated.userId,
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 d6dc67143892..664d61acf7cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -3747,6 +3747,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void notifyBiometricAuthModeChanged() {
mDozeServiceHost.updateDozing();
+ if (mBiometricUnlockController.getMode()
+ == BiometricUnlockController.MODE_DISMISS_BOUNCER) {
+ // Don't update the scrim controller at this time, in favor of the transition repository
+ // updating the scrim
+ return;
+ }
updateScrimController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index c72eb054c62c..39b5b5a4cef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable;
import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger;
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
@@ -288,10 +289,14 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da
* @param mobileContext possibly mcc/mnc overridden mobile context
* @param subId the subscriptionId for this mobile view
*/
- public void addModernMobileView(Context mobileContext, int subId) {
+ public void addModernMobileView(
+ Context mobileContext,
+ MobileViewLogger mobileViewLogger,
+ int subId) {
Log.d(TAG, "addModernMobileView (subId=" + subId + ")");
ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
mobileContext,
+ mobileViewLogger,
"mobile",
mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index b88531e59568..ae715b3f20c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -429,7 +429,6 @@ public class DozeParameters implements
}
dispatchAlwaysOnEvent();
- mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
}
@Override
@@ -469,6 +468,7 @@ public class DozeParameters implements
for (Callback callback : mCallbacks) {
callback.onAlwaysOnChange();
}
+ mScreenOffAnimationController.onAlwaysOnChanged(getAlwaysOn());
}
private boolean getPostureSpecificBool(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 753032c2ee01..3268032becf8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -59,7 +59,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.fragment.StatusBarIconBlocklistKt;
-import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventAnimator;
+import com.android.systemui.statusbar.phone.fragment.StatusBarSystemEventDefaultAnimator;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -75,6 +75,8 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
+import kotlin.Unit;
+
/** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
private static final String TAG = "KeyguardStatusBarViewController";
@@ -123,7 +125,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
public void onDensityOrFontScaleChanged() {
mView.loadDimens();
// The animator is dependent on resources for offsets
- mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, getResources());
+ mSystemEventAnimator =
+ getSystemEventAnimator(mSystemEventAnimator.isAnimationRunning());
}
@Override
@@ -248,7 +251,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private int mStatusBarState;
private boolean mDozing;
private boolean mShowingKeyguardHeadsUp;
- private StatusBarSystemEventAnimator mSystemEventAnimator;
+ private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
+ private float mSystemEventAnimatorAlpha = 1;
/**
* The alpha value to be set on the View. If -1, this value is to be ignored.
@@ -324,7 +328,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mView.setKeyguardUserAvatarEnabled(
!mStatusBarUserChipViewModel.getChipEnabled());
- mSystemEventAnimator = new StatusBarSystemEventAnimator(mView, r);
+ mSystemEventAnimator = getSystemEventAnimator(/* isAnimationRunning */ false);
mDisableStateTracker = new DisableStateTracker(
/* mask1= */ DISABLE_SYSTEM_INFO,
@@ -480,6 +484,10 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
* (1.0f - mKeyguardHeadsUpShowingAmount);
}
+ if (mSystemEventAnimator.isAnimationRunning()) {
+ newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha);
+ }
+
boolean hideForBypass =
mFirstBypassAttempt && mKeyguardUpdateMonitor.shouldListenForFace()
|| mDelayShowingKeyguardStatusBar;
@@ -488,7 +496,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
&& !mDozing
&& !hideForBypass
&& !mDisableStateTracker.isDisabled()
- ? View.VISIBLE : View.INVISIBLE;
+ ? View.VISIBLE : View.INVISIBLE;
updateViewState(newAlpha, newVisibility);
}
@@ -614,4 +622,15 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
updateBlockedIcons();
}
};
+
+ private StatusBarSystemEventDefaultAnimator getSystemEventAnimator(boolean isAnimationRunning) {
+ return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> {
+ mSystemEventAnimatorAlpha = alpha;
+ updateViewState();
+ return Unit.INSTANCE;
+ }, (translationX) -> {
+ mView.setTranslationX(translationX);
+ return Unit.INSTANCE;
+ }, isAnimationRunning);
+ }
}
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 fb8bf523f625..9fb942c7b740 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+
import static java.lang.Float.isNaN;
import android.animation.Animator;
@@ -53,9 +55,14 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
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.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.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +78,8 @@ import java.util.function.Consumer;
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
@@ -197,6 +206,7 @@ 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;
@@ -251,6 +261,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private boolean mWakeLockHeld;
private boolean mKeyguardOccluded;
+ private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private CoroutineDispatcher mMainDispatcher;
+ private boolean mIsBouncerToGoneTransitionRunning = false;
+ private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
+ private final Consumer<Float> mScrimAlphaConsumer =
+ (Float alpha) -> {
+ mScrimInFront.setViewAlpha(mInFrontAlpha);
+ mNotificationsScrim.setViewAlpha(mNotificationsAlpha);
+ mBehindAlpha = alpha;
+ mScrimBehind.setViewAlpha(alpha);
+ };
+
+ Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
+
@Inject
public ScrimController(
LightBarController lightBarController,
@@ -265,13 +289,18 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
@Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ SysuiStatusBarStateController sysuiStatusBarStateController,
+ @Main CoroutineDispatcher mainDispatcher) {
mScrimStateListener = lightBarController::setScrimState;
mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
mKeyguardStateController = keyguardStateController;
mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mStatusBarStateController = sysuiStatusBarStateController;
mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
mHandler = handler;
mMainExecutor = mainExecutor;
@@ -304,6 +333,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
});
mColors = new GradientColors();
+ mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mMainDispatcher = mainDispatcher;
}
/**
@@ -343,6 +375,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
for (ScrimState state : ScrimState.values()) {
state.prepare(state);
}
+
+ // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure
+ // to report back that keyguard has faded away. This fixes cases where the scrim state was
+ // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl
+ mPrimaryBouncerToGoneTransition =
+ (TransitionStep step) -> {
+ TransitionState state = step.getTransitionState();
+
+ mIsBouncerToGoneTransitionRunning = state == TransitionState.RUNNING;
+
+ if (state == TransitionState.STARTED) {
+ setExpansionAffectsAlpha(false);
+ transitionTo(ScrimState.UNLOCKED);
+ }
+
+ if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) {
+ setExpansionAffectsAlpha(true);
+ if (mKeyguardStateController.isKeyguardFadingAway()) {
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ }
+ };
+
+ collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
+ mPrimaryBouncerToGoneTransition, mMainDispatcher);
+ collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha(),
+ mScrimAlphaConsumer, mMainDispatcher);
}
// TODO(b/270984686) recompute scrim height accurately, based on shade contents.
@@ -372,6 +431,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
public void transitionTo(ScrimState state, Callback callback) {
+ if (mIsBouncerToGoneTransitionRunning) {
+ Log.i(TAG, "Skipping transition to: " + state
+ + " while mIsBouncerToGoneTransitionRunning");
+ return;
+ }
if (state == mState) {
// Call the callback anyway, unless it's already enqueued
if (callback != null && mCallback != callback) {
@@ -1049,7 +1113,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mBehindAlpha = 1;
}
// Prevent notification scrim flicker when transitioning away from keyguard.
- if (mKeyguardStateController.isKeyguardGoingAway()) {
+ if (mKeyguardStateController.isKeyguardGoingAway()
+ && !mStatusBarStateController.leaveOpenOnKeyguardHide()) {
mNotificationsAlpha = 0;
}
@@ -1138,7 +1203,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
Trace.traceCounter(Trace.TRACE_TAG_APP, getScrimName(scrimView) + "_tint",
Color.alpha(tint));
scrimView.setTint(tint);
- scrimView.setViewAlpha(alpha);
+ if (!mIsBouncerToGoneTransitionRunning) {
+ scrimView.setViewAlpha(alpha);
+ }
} else {
scrim.setAlpha(alpha);
}
@@ -1486,6 +1553,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
public void setKeyguardOccluded(boolean keyguardOccluded) {
+ if (mKeyguardOccluded == keyguardOccluded) {
+ return;
+ }
mKeyguardOccluded = keyguardOccluded;
updateScrims();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 11863627218e..04cc8ce792d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -569,7 +569,10 @@ public interface StatusBarIconController {
mGroup.addView(view, index, onCreateLayoutParams());
if (mIsInDemoMode) {
- mDemoStatusIcons.addModernMobileView(mContext, subId);
+ mDemoStatusIcons.addModernMobileView(
+ mContext,
+ mMobileIconsViewModel.getLogger(),
+ subId);
}
return view;
@@ -601,6 +604,7 @@ public interface StatusBarIconController {
return ModernStatusBarMobileView
.constructAndBind(
mobileContext,
+ mMobileIconsViewModel.getLogger(),
slot,
mMobileIconsViewModel.viewModelForSub(subId, mLocation)
);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index f6c0da8da8c0..833cb93f62e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -79,6 +79,18 @@ public class StatusBarIconHolder {
private @IconType int mType = TYPE_ICON;
private int mTag = 0;
+ /** Returns a human-readable string representing the given type. */
+ public static String getTypeString(@IconType int type) {
+ switch(type) {
+ case TYPE_ICON: return "ICON";
+ case TYPE_WIFI: return "WIFI_OLD";
+ case TYPE_MOBILE: return "MOBILE_OLD";
+ case TYPE_MOBILE_NEW: return "MOBILE_NEW";
+ case TYPE_WIFI_NEW: return "WIFI_NEW";
+ default: return "UNKNOWN";
+ }
+ }
+
private StatusBarIconHolder() {
}
@@ -230,4 +242,11 @@ public class StatusBarIconHolder {
public int getTag() {
return mTag;
}
+
+ @Override
+ public String toString() {
+ return "StatusBarIconHolder(type=" + getTypeString(mType)
+ + " tag=" + getTag()
+ + " visible=" + isVisible() + ")";
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index 8800b05fadb7..565481a20d95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -27,6 +27,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/** A class holding the list of all the system icons that could be shown in the status bar. */
public class StatusBarIconList {
@@ -302,7 +303,7 @@ public class StatusBarIconList {
@Override
public String toString() {
- return String.format("(%s) %s", mName, subSlotsString());
+ return String.format("(%s) holder=%s %s", mName, mHolder, subSlotsString());
}
private String subSlotsString() {
@@ -310,7 +311,10 @@ public class StatusBarIconList {
return "";
}
- return "" + mSubSlots.size() + " subSlots";
+ return "| " + mSubSlots.size() + " subSlots: "
+ + mSubSlots.stream()
+ .map(StatusBarIconHolder::toString)
+ .collect(Collectors.joining("|"));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
index 79c0984d9bf7..d30d0e25bd8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt
@@ -22,6 +22,7 @@ import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator.AlphaP
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController.StatusBarViewsCenterProvider
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import javax.inject.Inject
import kotlin.math.max
@@ -29,9 +30,13 @@ import kotlin.math.max
@SysUIUnfoldScope
class StatusBarMoveFromCenterAnimationController @Inject constructor(
private val progressProvider: ScopedUnfoldTransitionProgressProvider,
+ private val currentActivityTypeProvider: CurrentActivityTypeProvider,
windowManager: WindowManager
) {
+ // Whether we're on home activity. Updated only when the animation starts.
+ private var isOnHomeActivity: Boolean? = null
+
private val transitionListener = TransitionListener()
private val moveFromCenterAnimator = UnfoldMoveFromCenterAnimator(
windowManager,
@@ -60,6 +65,10 @@ class StatusBarMoveFromCenterAnimationController @Inject constructor(
}
private inner class TransitionListener : TransitionProgressListener {
+ override fun onTransitionStarted() {
+ isOnHomeActivity = currentActivityTypeProvider.isHomeActivity
+ }
+
override fun onTransitionProgress(progress: Float) {
moveFromCenterAnimator.onTransitionProgress(progress)
}
@@ -68,11 +77,23 @@ class StatusBarMoveFromCenterAnimationController @Inject constructor(
// Reset translations when transition is stopped/cancelled
// (e.g. the transition could be cancelled mid-way when rotating the screen)
moveFromCenterAnimator.onTransitionProgress(1f)
+ isOnHomeActivity = null
}
}
- private class StatusBarIconsAlphaProvider : AlphaProvider {
+
+ /**
+ * In certain cases, an alpha is applied based on the progress.
+ *
+ * This mainly happens to hide the statusbar during the unfold animation while on apps, as the
+ * bounds of the app "collapse" to the center, but the statusbar doesn't.
+ * While on launcher, this alpha is not applied.
+ */
+ private inner class StatusBarIconsAlphaProvider : AlphaProvider {
override fun getAlpha(progress: Float): Float {
+ if (isOnHomeActivity == true) {
+ return 1.0f
+ }
return max(
0f,
(progress - ICONS_START_APPEARING_PROGRESS) / (1 - ICONS_START_APPEARING_PROGRESS)
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 726b2344309f..edfc95fcc2e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.phone;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOptions;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.PendingIntent;
@@ -579,8 +581,14 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
entry.getKey());
mCentralSurfaces.wakeUpForFullScreenIntent();
- fullScreenIntent.send();
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ fullScreenIntent.sendAndReturnResult(null, 0, null, null, null, null,
+ options.toBundle());
entry.notifyFullScreenIntentLaunched();
+
mMetricsLogger.count("note_fullscreen", 1);
String activityName;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
index c04ea36b3d8d..5903fa3d5bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt
@@ -26,19 +26,39 @@ import com.android.systemui.statusbar.events.STATUS_BAR_X_MOVE_IN
import com.android.systemui.statusbar.events.STATUS_BAR_X_MOVE_OUT
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
+import com.android.systemui.util.doOnCancel
+import com.android.systemui.util.doOnEnd
+
+/**
+ * An implementation of [StatusBarSystemEventDefaultAnimator], applying the onAlphaChanged and
+ * onTranslationXChanged callbacks directly to the provided animatedView.
+ */
+class StatusBarSystemEventAnimator @JvmOverloads constructor(
+ val animatedView: View,
+ resources: Resources,
+ isAnimationRunning: Boolean = false
+) : StatusBarSystemEventDefaultAnimator(
+ resources = resources,
+ onAlphaChanged = animatedView::setAlpha,
+ onTranslationXChanged = animatedView::setTranslationX,
+ isAnimationRunning = isAnimationRunning
+)
/**
* Tied directly to [SystemStatusAnimationScheduler]. Any StatusBar-like thing (keyguard, collapsed
- * status bar fragment), can just feed this an animatable view to get the default system status
- * animation.
+ * status bar fragment), can use this Animator to get the default system status animation. It simply
+ * needs to implement the onAlphaChanged and onTranslationXChanged callbacks.
*
* This animator relies on resources, and should be recreated whenever resources are updated. While
* this class could be used directly as the animation callback, it's probably best to forward calls
* to it so that it can be recreated at any moment without needing to remove/add callback.
*/
-class StatusBarSystemEventAnimator(
- val animatedView: View,
- resources: Resources
+
+open class StatusBarSystemEventDefaultAnimator @JvmOverloads constructor(
+ resources: Resources,
+ private val onAlphaChanged: (Float) -> Unit,
+ private val onTranslationXChanged: (Float) -> Unit,
+ var isAnimationRunning: Boolean = false
) : SystemStatusAnimationCallback {
private val translationXIn: Int = resources.getDimensionPixelSize(
R.dimen.ongoing_appops_chip_animation_in_status_bar_translation_x)
@@ -46,18 +66,19 @@ class StatusBarSystemEventAnimator(
R.dimen.ongoing_appops_chip_animation_out_status_bar_translation_x)
override fun onSystemEventAnimationBegin(): Animator {
+ isAnimationRunning = true
val moveOut = ValueAnimator.ofFloat(0f, 1f).apply {
duration = 23.frames
interpolator = STATUS_BAR_X_MOVE_OUT
addUpdateListener {
- animatedView.translationX = -(translationXIn * animatedValue as Float)
+ onTranslationXChanged(-(translationXIn * animatedValue as Float))
}
}
val alphaOut = ValueAnimator.ofFloat(1f, 0f).apply {
duration = 8.frames
interpolator = null
addUpdateListener {
- animatedView.alpha = animatedValue as Float
+ onAlphaChanged(animatedValue as Float)
}
}
@@ -67,13 +88,13 @@ class StatusBarSystemEventAnimator(
}
override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
- animatedView.translationX = translationXOut.toFloat()
+ onTranslationXChanged(translationXOut.toFloat())
val moveIn = ValueAnimator.ofFloat(1f, 0f).apply {
duration = 23.frames
startDelay = 7.frames
interpolator = STATUS_BAR_X_MOVE_IN
addUpdateListener {
- animatedView.translationX = translationXOut * animatedValue as Float
+ onTranslationXChanged(translationXOut * animatedValue as Float)
}
}
val alphaIn = ValueAnimator.ofFloat(0f, 1f).apply {
@@ -81,13 +102,14 @@ class StatusBarSystemEventAnimator(
startDelay = 11.frames
interpolator = null
addUpdateListener {
- animatedView.alpha = animatedValue as Float
+ onAlphaChanged(animatedValue as Float)
}
}
val animatorSet = AnimatorSet()
animatorSet.playTogether(moveIn, alphaIn)
-
+ animatorSet.doOnEnd { isAnimationRunning = false }
+ animatorSet.doOnCancel { isAnimationRunning = false }
return animatorSet
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.kt
new file mode 100644
index 000000000000..e594a8a5efd9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/MobileViewLog.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.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Logs for changes with the new mobile views. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class MobileViewLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 44647515a6e5..adfea80715a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -148,5 +148,19 @@ abstract class StatusBarPipelineModule {
fun provideMobileInputLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("MobileInputLog", 100)
}
+
+ @Provides
+ @SysUISingleton
+ @MobileViewLog
+ fun provideMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("MobileViewLog", 100)
+ }
+
+ @Provides
+ @SysUISingleton
+ @VerboseMobileViewLog
+ fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
+ return factory.create("VerboseMobileViewLog", 100)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.kt
new file mode 100644
index 000000000000..b98789807dd3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/VerboseMobileViewLog.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.statusbar.pipeline.dagger
+
+import javax.inject.Qualifier
+
+/** Logs for **verbose** changes with the new mobile views. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class VerboseMobileViewLog
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 3cbd2b76c248..159f689de370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.shared
+package com.android.systemui.statusbar.pipeline.mobile.data
import android.net.Network
import android.net.NetworkCapabilities
@@ -133,24 +133,6 @@ constructor(
)
}
- fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter updated internally: $str1" },
- )
- }
-
- fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
- )
- }
-
fun logCarrierConfigChanged(subId: Int) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
index bb3b9b2166c3..efdce062bb37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepository.kt
@@ -30,8 +30,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
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 96b96f14d6aa..e182bc66081a 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
@@ -36,6 +36,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
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.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -47,7 +48,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameMo
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
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.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
import javax.inject.Inject
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 53a208cd171e..f97e41c018f4 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
@@ -45,11 +45,11 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
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.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index da63ab10f733..075e6ec11ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -22,7 +22,6 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import java.io.PrintWriter
import javax.inject.Inject
@@ -55,17 +54,14 @@ constructor(
interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
private val iconsViewModelFactory: MobileIconsViewModel.Factory,
- private val logger: MobileInputLogger,
+ private val logger: MobileViewLogger,
@Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable {
private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions
- .mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
- .distinctUntilChanged()
- .onEach { logger.logUiAdapterSubIdsUpdated(it) }
+ interactor.filteredSubscriptions.mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
/**
* We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
@@ -75,7 +71,10 @@ constructor(
* NOTE: this should go away as the view presenter learns more about this data pipeline
*/
private val mobileSubIdsState: StateFlow<List<Int>> =
- mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+ mobileSubIds
+ .distinctUntilChanged()
+ .onEach { logger.logUiAdapterSubIdsUpdated(it) }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
/** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
new file mode 100644
index 000000000000..90dff23c637c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.ui
+
+import android.view.View
+import com.android.systemui.Dumpable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Logs for changes with the new mobile views. */
+@SysUISingleton
+class MobileViewLogger
+@Inject
+constructor(
+ @MobileViewLog private val buffer: LogBuffer,
+ dumpManager: DumpManager,
+) : Dumpable {
+ init {
+ dumpManager.registerNormalDumpable(this)
+ }
+
+ private val collectionStatuses = mutableMapOf<String, Boolean>()
+
+ fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter updated internally: $str1" },
+ )
+ }
+
+ fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { str1 = subs.toString() },
+ { "Sub IDs in MobileUiAdapter being sent to icon controller: $str1" },
+ )
+ }
+
+ fun logNewViewBinding(view: View, viewModel: LocationBasedMobileViewModel) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "New view binding. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ fun logCollectionStarted(view: View, viewModel: LocationBasedMobileViewModel) {
+ collectionStatuses[view.getIdForLogging()] = true
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "Collection started. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ fun logCollectionStopped(view: View, viewModel: LocationBasedMobileViewModel) {
+ collectionStatuses[view.getIdForLogging()] = false
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ {
+ str1 = view.getIdForLogging()
+ str2 = viewModel.getIdForLogging()
+ str3 = viewModel.locationName
+ },
+ { "Collection stopped. viewId=$str1, viewModelId=$str2, viewModelLocation=$str3" },
+ )
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("Collection statuses per view:---")
+ collectionStatuses.forEach { viewId, isCollecting ->
+ pw.println("viewId=$viewId, isCollecting=$isCollecting")
+ }
+ }
+
+ companion object {
+ fun Any.getIdForLogging(): String {
+ // The identityHashCode is guaranteed to be constant for the lifetime of the object.
+ return Integer.toHexString(System.identityHashCode(this))
+ }
+ }
+}
+
+private const val TAG = "MobileViewLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt
new file mode 100644
index 000000000000..f67bc8f14447
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.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.statusbar.pipeline.mobile.ui
+
+import android.view.View
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
+import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
+import javax.inject.Inject
+
+/**
+ * Logs for **verbose** changes with the new mobile views.
+ *
+ * This is a hopefully temporary log until we resolve some open bugs (b/267236367, b/269565345,
+ * b/270300839).
+ */
+@SysUISingleton
+class VerboseMobileViewLogger
+@Inject
+constructor(
+ @VerboseMobileViewLog private val buffer: LogBuffer,
+) {
+ fun logBinderReceivedSignalIcon(parentView: View, subId: Int, icon: SignalIconModel) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = parentView.getIdForLogging()
+ int1 = subId
+ int2 = icon.level
+ bool1 = icon.showExclamationMark
+ },
+ {
+ "Binder[subId=$int1, viewId=$str1] received new signal icon: " +
+ "level=$int2 showExclamation=$bool1"
+ },
+ )
+ }
+
+ fun logBinderReceivedNetworkTypeIcon(parentView: View, subId: Int, icon: Icon.Resource?) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = parentView.getIdForLogging()
+ int1 = subId
+ bool1 = icon != null
+ int2 = icon?.res ?: -1
+ },
+ {
+ "Binder[subId=$int1, viewId=$str1] received new network type icon: " +
+ if (bool1) "resId=$int2" else "null"
+ },
+ )
+ }
+}
+
+private const val TAG = "VerboseMobileViewLogger"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index db585e68d185..5b7d45b55c5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -36,8 +36,10 @@ import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -48,6 +50,7 @@ object MobileIconBinder {
fun bind(
view: ViewGroup,
viewModel: LocationBasedMobileViewModel,
+ logger: MobileViewLogger,
): ModernStatusBarViewBinding {
val mobileGroupView = view.requireViewById<ViewGroup>(R.id.mobile_group)
val activityContainer = view.requireViewById<View>(R.id.inout_container)
@@ -70,8 +73,13 @@ object MobileIconBinder {
val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ var isCollecting: Boolean = false
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ logger.logCollectionStarted(view, viewModel)
+ isCollecting = true
+
launch {
visibilityState.collect { state ->
when (state) {
@@ -96,6 +104,11 @@ object MobileIconBinder {
// Set the icon for the triangle
launch {
viewModel.icon.distinctUntilChanged().collect { icon ->
+ viewModel.verboseLogger?.logBinderReceivedSignalIcon(
+ view,
+ viewModel.subscriptionId,
+ icon,
+ )
mobileDrawable.level =
SignalDrawable.getState(
icon.level,
@@ -114,6 +127,11 @@ object MobileIconBinder {
// Set the network type icon
launch {
viewModel.networkTypeIcon.distinctUntilChanged().collect { dataTypeId ->
+ viewModel.verboseLogger?.logBinderReceivedNetworkTypeIcon(
+ view,
+ viewModel.subscriptionId,
+ dataTypeId,
+ )
dataTypeId?.let { IconViewBinder.bind(dataTypeId, networkTypeView) }
networkTypeView.visibility = if (dataTypeId != null) VISIBLE else GONE
}
@@ -150,6 +168,13 @@ object MobileIconBinder {
}
launch { decorTint.collect { tint -> dotView.setDecorColor(tint) } }
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ logger.logCollectionStopped(view, viewModel)
+ }
}
}
@@ -175,6 +200,10 @@ object MobileIconBinder {
}
decorTint.value = newTint
}
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index ed9a1884a7b4..4144293d5ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -20,6 +20,8 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView.getVisibleStateString
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
@@ -31,6 +33,15 @@ class ModernStatusBarMobileView(
var subId: Int = -1
+ override fun toString(): String {
+ return "ModernStatusBarMobileView(" +
+ "slot='$slot', " +
+ "subId=$subId, " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
companion object {
/**
@@ -40,6 +51,7 @@ class ModernStatusBarMobileView(
@JvmStatic
fun constructAndBind(
context: Context,
+ logger: MobileViewLogger,
slot: String,
viewModel: LocationBasedMobileViewModel,
): ModernStatusBarMobileView {
@@ -48,7 +60,8 @@ class ModernStatusBarMobileView(
as ModernStatusBarMobileView)
.also {
it.subId = viewModel.subscriptionId
- it.initView(slot) { MobileIconBinder.bind(it, viewModel) }
+ it.initView(slot) { MobileIconBinder.bind(it, viewModel, logger) }
+ logger.logNewViewBinding(it, viewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
index 8e103f7bee2f..f775940140cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import android.graphics.Color
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
/**
* A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
@@ -26,11 +27,15 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
*
* @param commonImpl for convenience, this class wraps a base interface that can provides all of the
* common implementations between locations. See [MobileIconViewModel]
+ * @property locationName the name of the location of this VM, used for logging.
+ * @property verboseLogger an optional logger to log extremely verbose view updates.
*/
abstract class LocationBasedMobileViewModel(
val commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
debugTint: Int,
+ val locationName: String,
+ val verboseLogger: VerboseMobileViewLogger?,
) : MobileIconViewModelCommon by commonImpl {
val useDebugColoring: Boolean = statusBarPipelineFlags.useDebugColoring()
@@ -45,11 +50,16 @@ abstract class LocationBasedMobileViewModel(
fun viewModelForLocation(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
loc: StatusBarLocation,
): LocationBasedMobileViewModel =
when (loc) {
StatusBarLocation.HOME ->
- HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ HomeMobileIconViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ verboseMobileViewLogger,
+ )
StatusBarLocation.KEYGUARD ->
KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
@@ -60,20 +70,41 @@ abstract class LocationBasedMobileViewModel(
class HomeMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
+ verboseMobileViewLogger: VerboseMobileViewLogger,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.CYAN)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.CYAN,
+ locationName = "Home",
+ verboseMobileViewLogger,
+ )
class QsMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.GREEN)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.GREEN,
+ locationName = "QS",
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
class KeyguardMobileIconViewModel(
commonImpl: MobileIconViewModelCommon,
statusBarPipelineFlags: StatusBarPipelineFlags,
) :
MobileIconViewModelCommon,
- LocationBasedMobileViewModel(commonImpl, statusBarPipelineFlags, debugTint = Color.MAGENTA)
+ LocationBasedMobileViewModel(
+ commonImpl,
+ statusBarPipelineFlags,
+ debugTint = Color.MAGENTA,
+ locationName = "Keyguard",
+ // Only do verbose logging for the Home location.
+ verboseLogger = null,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 049627899eff..dbb534b24471 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -49,7 +49,7 @@ interface MobileIconViewModelCommon {
val contentDescription: Flow<ContentDescription>
val roaming: Flow<Boolean>
/** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
- val networkTypeIcon: Flow<Icon?>
+ val networkTypeIcon: Flow<Icon.Resource?>
val activityInVisible: Flow<Boolean>
val activityOutVisible: Flow<Boolean>
val activityContainerVisible: Flow<Boolean>
@@ -161,7 +161,7 @@ constructor(
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
- override val networkTypeIcon: Flow<Icon?> =
+ override val networkTypeIcon: Flow<Icon.Resource?> =
combine(
iconInteractor.networkTypeIconGroup,
showNetworkTypeIcon,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 8cb52af336da..2b90065284d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -23,6 +23,8 @@ import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import javax.inject.Inject
@@ -39,6 +41,8 @@ class MobileIconsViewModel
@Inject
constructor(
val subscriptionIdsFlow: StateFlow<List<Int>>,
+ val logger: MobileViewLogger,
+ private val verboseLogger: VerboseMobileViewLogger,
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
@@ -66,6 +70,7 @@ constructor(
return LocationBasedMobileViewModel.viewModelForLocation(
common,
statusBarPipelineFlags,
+ verboseLogger,
location,
)
}
@@ -79,6 +84,8 @@ constructor(
class Factory
@Inject
constructor(
+ private val logger: MobileViewLogger,
+ private val verboseLogger: VerboseMobileViewLogger,
private val interactor: MobileIconsInteractor,
private val airplaneModeInteractor: AirplaneModeInteractor,
private val constants: ConnectivityConstants,
@@ -88,6 +95,8 @@ constructor(
fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
return MobileIconsViewModel(
subscriptionIdsFlow,
+ logger,
+ verboseLogger,
interactor,
airplaneModeInteractor,
constants,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
index f67876b50233..81f8683411ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/ModernStatusBarViewBinding.kt
@@ -37,4 +37,7 @@ interface ModernStatusBarViewBinding {
/** Notifies that the decor tint has been updated (used only for the dot). */
fun onDecorTintChanged(newTint: Int)
+
+ /** Returns true if the binding between the view and view-model is currently collecting. */
+ fun isCollecting(): Boolean
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index b1e28129a690..1a1340484bfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -36,7 +36,7 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) :
BaseStatusBarFrameLayout(context, attrs) {
private lateinit var slot: String
- private lateinit var binding: ModernStatusBarViewBinding
+ internal lateinit var binding: ModernStatusBarViewBinding
@StatusBarIconView.VisibleState
private var iconVisibleState: Int = STATE_HIDDEN
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 2aff12c8721d..9e8c814ca2a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarV
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -74,8 +75,12 @@ object WifiViewBinder {
val iconTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
val decorTint: MutableStateFlow<Int> = MutableStateFlow(viewModel.defaultColor)
+ var isCollecting: Boolean = false
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
+ isCollecting = true
+
launch {
visibilityState.collect { visibilityState ->
groupView.isVisible = visibilityState == STATE_ICON
@@ -127,6 +132,12 @@ object WifiViewBinder {
airplaneSpacer.isVisible = visible
}
}
+
+ try {
+ awaitCancellation()
+ } finally {
+ isCollecting = false
+ }
}
}
@@ -152,6 +163,10 @@ object WifiViewBinder {
}
decorTint.value = newTint
}
+
+ override fun isCollecting(): Boolean {
+ return isCollecting
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 7a734862fe1b..f23e10287164 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import com.android.systemui.R
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
@@ -33,6 +34,15 @@ class ModernStatusBarWifiView(
context: Context,
attrs: AttributeSet?,
) : ModernStatusBarView(context, attrs) {
+
+ override fun toString(): String {
+ return "ModernStatusBarWifiView(" +
+ "slot='$slot', " +
+ "isCollecting=${binding.isCollecting()}, " +
+ "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
+ "viewString=${super.toString()}"
+ }
+
companion object {
/**
* Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
@@ -45,12 +55,9 @@ class ModernStatusBarWifiView(
slot: String,
wifiViewModel: LocationBasedWifiViewModel,
): ModernStatusBarWifiView {
- return (
- LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
- as ModernStatusBarWifiView
- ).also {
- it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) }
- }
+ return (LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
+ as ModernStatusBarWifiView)
+ .also { it.initView(slot) { WifiViewBinder.bind(it, wifiViewModel) } }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 7acdaffb48c4..01fabcc8bc1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -22,13 +22,18 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
import android.annotation.Nullable;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Trace;
-import android.util.Log;
+import android.util.IndentingPrintWriter;
+
+import androidx.annotation.NonNull;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
+import java.io.PrintWriter;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -39,19 +44,19 @@ import javax.inject.Inject;
*/
@SysUISingleton
public final class DeviceStateRotationLockSettingController
- implements Listenable, RotationLockController.RotationLockControllerCallback {
-
- private static final String TAG = "DSRotateLockSettingCon";
+ implements Listenable, RotationLockController.RotationLockControllerCallback, Dumpable {
private final RotationPolicyWrapper mRotationPolicyWrapper;
private final DeviceStateManager mDeviceStateManager;
private final Executor mMainExecutor;
private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;
+ private final DeviceStateRotationLockSettingControllerLogger mLogger;
// On registration for DeviceStateCallback, we will receive a callback with the current state
// and this will be initialized.
private int mDeviceState = -1;
- @Nullable private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
+ @Nullable
+ private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
private DeviceStateRotationLockSettingsManager.DeviceStateRotationLockSettingsListener
mDeviceStateRotationLockSettingsListener;
@@ -60,21 +65,27 @@ public final class DeviceStateRotationLockSettingController
RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateManager deviceStateManager,
@Main Executor executor,
- DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager) {
+ DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager,
+ DeviceStateRotationLockSettingControllerLogger logger,
+ DumpManager dumpManager) {
mRotationPolicyWrapper = rotationPolicyWrapper;
mDeviceStateManager = deviceStateManager;
mMainExecutor = executor;
mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
+ mLogger = logger;
+ dumpManager.registerDumpable(this);
}
@Override
public void setListening(boolean listening) {
+ mLogger.logListeningChange(listening);
if (listening) {
// Note that this is called once with the initial state of the device, even if there
// is no user action.
mDeviceStateCallback = this::updateDeviceState;
mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
- mDeviceStateRotationLockSettingsListener = () -> readPersistedSetting(mDeviceState);
+ mDeviceStateRotationLockSettingsListener = () ->
+ readPersistedSetting("deviceStateRotationLockChange", mDeviceState);
mDeviceStateRotationLockSettingsManager.registerListener(
mDeviceStateRotationLockSettingsListener);
} else {
@@ -89,35 +100,28 @@ public final class DeviceStateRotationLockSettingController
}
@Override
- public void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible) {
- if (mDeviceState == -1) {
- Log.wtf(TAG, "Device state was not initialized.");
+ public void onRotationLockStateChanged(boolean newRotationLocked, boolean affordanceVisible) {
+ int deviceState = mDeviceState;
+ boolean currentRotationLocked = mDeviceStateRotationLockSettingsManager
+ .isRotationLocked(deviceState);
+ mLogger.logRotationLockStateChanged(deviceState, newRotationLocked, currentRotationLocked);
+ if (deviceState == -1) {
return;
}
-
- if (rotationLocked
- == mDeviceStateRotationLockSettingsManager.isRotationLocked(mDeviceState)) {
- Log.v(TAG, "Rotation lock same as the current setting, no need to update.");
+ if (newRotationLocked == currentRotationLocked) {
return;
}
-
- saveNewRotationLockSetting(rotationLocked);
+ saveNewRotationLockSetting(newRotationLocked);
}
private void saveNewRotationLockSetting(boolean isRotationLocked) {
- Log.v(
- TAG,
- "saveNewRotationLockSetting [state="
- + mDeviceState
- + "] [isRotationLocked="
- + isRotationLocked
- + "]");
-
- mDeviceStateRotationLockSettingsManager.updateSetting(mDeviceState, isRotationLocked);
+ int deviceState = mDeviceState;
+ mLogger.logSaveNewRotationLockSetting(isRotationLocked, deviceState);
+ mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked);
}
private void updateDeviceState(int state) {
- Log.v(TAG, "updateDeviceState [state=" + state + "]");
+ mLogger.logUpdateDeviceState(mDeviceState, state);
if (Trace.isEnabled()) {
Trace.traceBegin(
Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
@@ -127,22 +131,26 @@ public final class DeviceStateRotationLockSettingController
return;
}
- readPersistedSetting(state);
+ readPersistedSetting("updateDeviceState", state);
} finally {
Trace.endSection();
}
}
- private void readPersistedSetting(int state) {
+ private void readPersistedSetting(String caller, int state) {
int rotationLockSetting =
mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
+ boolean shouldBeLocked = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
+ boolean isLocked = mRotationPolicyWrapper.isRotationLocked();
+
+ mLogger.readPersistedSetting(caller, state, rotationLockSetting, shouldBeLocked, isLocked);
+
if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) {
// This should not happen. Device states that have an ignored setting, should also
// specify a fallback device state which is not ignored.
// We won't handle this device state. The same rotation lock setting as before should
// apply and any changes to the rotation lock setting will be written for the previous
// valid device state.
- Log.w(TAG, "Missing fallback. Ignoring new device state: " + state);
return;
}
@@ -150,9 +158,18 @@ public final class DeviceStateRotationLockSettingController
mDeviceState = state;
// Update the rotation policy, if needed, for this new device state
- boolean newRotationLockSetting = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
- if (newRotationLockSetting != mRotationPolicyWrapper.isRotationLocked()) {
- mRotationPolicyWrapper.setRotationLock(newRotationLockSetting);
+ if (shouldBeLocked != isLocked) {
+ mRotationPolicyWrapper.setRotationLock(shouldBeLocked);
}
}
+
+ @Override
+ public void dump(@NonNull PrintWriter printWriter, @NonNull String[] args) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
+ mDeviceStateRotationLockSettingsManager.dump(pw);
+ pw.println("DeviceStateRotationLockSettingController");
+ pw.increaseIndent();
+ pw.println("mDeviceState: " + mDeviceState);
+ pw.decreaseIndent();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt
new file mode 100644
index 000000000000..aa502bc48149
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.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.statusbar.policy
+
+import android.content.Context
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
+import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED
+import com.android.internal.R
+import com.android.systemui.log.dagger.DeviceStateAutoRotationLog
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.VERBOSE
+import javax.inject.Inject
+
+class DeviceStateRotationLockSettingControllerLogger
+@Inject
+constructor(@DeviceStateAutoRotationLog private val logBuffer: LogBuffer, context: Context) {
+
+ private val foldedStates = context.resources.getIntArray(R.array.config_foldedDeviceStates)
+ private val halfFoldedStates =
+ context.resources.getIntArray(R.array.config_halfFoldedDeviceStates)
+ private val unfoldedStates = context.resources.getIntArray(R.array.config_openDeviceStates)
+
+ fun logListeningChange(listening: Boolean) {
+ logBuffer.log(TAG, VERBOSE, { bool1 = listening }, { "setListening: $bool1" })
+ }
+
+ fun logRotationLockStateChanged(
+ state: Int,
+ newRotationLocked: Boolean,
+ currentRotationLocked: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ int1 = state
+ bool1 = newRotationLocked
+ bool2 = currentRotationLocked
+ },
+ {
+ "onRotationLockStateChanged: " +
+ "state=$int1 [${int1.toDevicePostureString()}], " +
+ "newRotationLocked=$bool1, " +
+ "currentRotationLocked=$bool2"
+ }
+ )
+ }
+
+ fun logSaveNewRotationLockSetting(isRotationLocked: Boolean, state: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ bool1 = isRotationLocked
+ int1 = state
+ },
+ { "saveNewRotationLockSetting: isRotationLocked=$bool1, state=$int1" }
+ )
+ }
+
+ fun logUpdateDeviceState(currentState: Int, newState: Int) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ int1 = currentState
+ int2 = newState
+ },
+ {
+ "updateDeviceState: " +
+ "current=$int1 [${int1.toDevicePostureString()}], " +
+ "new=$int2 [${int2.toDevicePostureString()}]"
+ }
+ )
+ }
+
+ fun readPersistedSetting(
+ caller: String,
+ state: Int,
+ rotationLockSetting: Int,
+ shouldBeLocked: Boolean,
+ isLocked: Boolean
+ ) {
+ logBuffer.log(
+ TAG,
+ VERBOSE,
+ {
+ str1 = caller
+ int1 = state
+ int2 = rotationLockSetting
+ bool1 = shouldBeLocked
+ bool2 = isLocked
+ },
+ {
+ "readPersistedSetting: " +
+ "caller=$str1, " +
+ "state=$int1 [${int1.toDevicePostureString()}], " +
+ "rotationLockSettingForState: ${int2.toRotationLockSettingString()}, " +
+ "shouldBeLocked=$bool1, " +
+ "isLocked=$bool2"
+ }
+ )
+ }
+
+ private fun Int.toDevicePostureString(): String {
+ return when (this) {
+ in foldedStates -> "Folded"
+ in unfoldedStates -> "Unfolded"
+ in halfFoldedStates -> "Half-Folded"
+ -1 -> "Uninitialized"
+ else -> "Unknown"
+ }
+ }
+}
+
+private fun Int.toRotationLockSettingString(): String {
+ return when (this) {
+ DEVICE_STATE_ROTATION_LOCK_IGNORED -> "IGNORED"
+ DEVICE_STATE_ROTATION_LOCK_LOCKED -> "LOCKED"
+ DEVICE_STATE_ROTATION_LOCK_UNLOCKED -> "UNLOCKED"
+ else -> "Unknown"
+ }
+}
+
+private const val TAG = "DSRotateLockSettingCon"
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 030c54fbd87d..9952cfd4e85b 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -62,8 +62,6 @@ constructor(
BluetoothAdapter.OnMetadataChangedListener {
private val stylusCallbacks: CopyOnWriteArrayList<StylusCallback> = CopyOnWriteArrayList()
- private val stylusBatteryCallbacks: CopyOnWriteArrayList<StylusBatteryCallback> =
- CopyOnWriteArrayList()
// This map should only be accessed on the handler
private val inputDeviceAddressMap: MutableMap<Int, String?> = ArrayMap()
@@ -106,14 +104,6 @@ constructor(
stylusCallbacks.remove(callback)
}
- fun registerBatteryCallback(callback: StylusBatteryCallback) {
- stylusBatteryCallbacks.add(callback)
- }
-
- fun unregisterBatteryCallback(callback: StylusBatteryCallback) {
- stylusBatteryCallbacks.remove(callback)
- }
-
override fun onInputDeviceAdded(deviceId: Int) {
if (!hasStarted) return
@@ -195,7 +185,7 @@ constructor(
"${device.address}: $isCharging"
}
- executeStylusBatteryCallbacks { cb ->
+ executeStylusCallbacks { cb ->
cb.onStylusBluetoothChargingStateChanged(inputDeviceId, device, isCharging)
}
}
@@ -221,7 +211,7 @@ constructor(
onStylusUsed()
}
- executeStylusBatteryCallbacks { cb ->
+ executeStylusCallbacks { cb ->
cb.onStylusUsiBatteryStateChanged(deviceId, eventTimeMillis, batteryState)
}
}
@@ -329,10 +319,6 @@ constructor(
stylusCallbacks.forEach(run)
}
- private fun executeStylusBatteryCallbacks(run: (cb: StylusBatteryCallback) -> Unit) {
- stylusBatteryCallbacks.forEach(run)
- }
-
private fun registerBatteryListener(deviceId: Int) {
try {
inputManager.addInputDeviceBatteryListener(deviceId, executor, this)
@@ -378,13 +364,6 @@ constructor(
fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {}
fun onStylusBluetoothDisconnected(deviceId: Int, btAddress: String) {}
fun onStylusFirstUsed() {}
- }
-
- /**
- * Callback interface to receive stylus battery events from the StylusManager. All callbacks are
- * runs on the same background handler.
- */
- interface StylusBatteryCallback {
fun onStylusBluetoothChargingStateChanged(
inputDeviceId: Int,
btDevice: BluetoothDevice,
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
index 27cafb10c07d..3667392b515e 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerStartable.kt
@@ -37,7 +37,7 @@ constructor(
private val inputManager: InputManager,
private val stylusUsiPowerUi: StylusUsiPowerUI,
private val featureFlags: FeatureFlags,
-) : CoreStartable, StylusManager.StylusCallback, StylusManager.StylusBatteryCallback {
+) : CoreStartable, StylusManager.StylusCallback {
override fun onStylusAdded(deviceId: Int) {
// On some devices, the addition of a new internal stylus indicates the use of a
@@ -74,7 +74,6 @@ constructor(
stylusUsiPowerUi.init()
stylusManager.registerCallback(this)
- stylusManager.registerBatteryCallback(this)
stylusManager.startListener()
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
index d5d3efd78d13..3a7ac9c8a8bd 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbAccessoryUriActivity.java
@@ -31,6 +31,9 @@ import android.view.WindowManager;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+
+import javax.inject.Inject;
/**
* If the attached USB accessory has a URL associated with it, and that URL is valid,
@@ -46,13 +49,27 @@ public class UsbAccessoryUriActivity extends AlertActivity
private UsbAccessory mAccessory;
private Uri mUri;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+
+ @Inject
+ UsbAccessoryUriActivity(DeviceProvisionedController deviceProvisionedController) {
+ mDeviceProvisionedController = deviceProvisionedController;
+ }
+
@Override
public void onCreate(Bundle icicle) {
- getWindow().addSystemFlags(
+ getWindow().addSystemFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- super.onCreate(icicle);
+ super.onCreate(icicle);
+
+ // Don't show this dialog during Setup Wizard
+ if (!mDeviceProvisionedController.isDeviceProvisioned()) {
+ Log.e(TAG, "device not provisioned");
+ finish();
+ return;
+ }
- Intent intent = getIntent();
+ Intent intent = getIntent();
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
String uriString = intent.getStringExtra("uri");
mUri = (uriString == null ? null : Uri.parse(uriString));
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
index 81ae6e851fb9..c72853ef37be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
@@ -115,6 +115,17 @@ public abstract class SysUIConcurrencyModule {
}
/**
+ * Provide a Long running Executor.
+ */
+ @Provides
+ @SysUISingleton
+ @LongRunning
+ public static DelayableExecutor provideLongRunningDelayableExecutor(
+ @LongRunning Looper looper) {
+ return new ExecutorImpl(looper);
+ }
+
+ /**
* Provide a Background-Thread Executor.
*/
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 76a01b918952..54b30300ba49 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -41,7 +41,7 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -71,17 +71,16 @@ public class ImageWallpaper extends WallpaperService {
private HandlerThread mWorker;
// used for most tasks (call canvas.drawBitmap, load/unload the bitmap)
- @Background
- private final DelayableExecutor mBackgroundExecutor;
+ @LongRunning
+ private final DelayableExecutor mLongExecutor;
// wait at least this duration before unloading the bitmap
private static final int DELAY_UNLOAD_BITMAP = 2000;
@Inject
- public ImageWallpaper(@Background DelayableExecutor backgroundExecutor,
- UserTracker userTracker) {
+ public ImageWallpaper(@LongRunning DelayableExecutor longExecutor, UserTracker userTracker) {
super();
- mBackgroundExecutor = backgroundExecutor;
+ mLongExecutor = longExecutor;
mUserTracker = userTracker;
}
@@ -131,7 +130,7 @@ public class ImageWallpaper extends WallpaperService {
setFixedSizeAllowed(true);
setShowForAllUsers(true);
mWallpaperLocalColorExtractor = new WallpaperLocalColorExtractor(
- mBackgroundExecutor,
+ mLongExecutor,
new WallpaperLocalColorExtractor.WallpaperLocalColorExtractorCallback() {
@Override
public void onColorsProcessed(List<RectF> regions,
@@ -230,7 +229,7 @@ public class ImageWallpaper extends WallpaperService {
}
private void drawFrame() {
- mBackgroundExecutor.execute(this::drawFrameSynchronized);
+ mLongExecutor.execute(this::drawFrameSynchronized);
}
private void drawFrameSynchronized() {
@@ -285,7 +284,7 @@ public class ImageWallpaper extends WallpaperService {
}
private void unloadBitmapIfNotUsed() {
- mBackgroundExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
+ mLongExecutor.execute(this::unloadBitmapIfNotUsedSynchronized);
}
private void unloadBitmapIfNotUsedSynchronized() {
@@ -381,7 +380,7 @@ public class ImageWallpaper extends WallpaperService {
* - the mini bitmap from color extractor is recomputed
* - the DELAY_UNLOAD_BITMAP has passed
*/
- mBackgroundExecutor.executeDelayed(
+ mLongExecutor.executeDelayed(
this::unloadBitmapIfNotUsedSynchronized, DELAY_UNLOAD_BITMAP);
}
// even if the bitmap cannot be loaded, call reportEngineShown
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
index 988fd710d2dc..1e8446f8df1d 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractor.java
@@ -29,7 +29,7 @@ import android.util.MathUtils;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
-import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.util.Assert;
import java.io.FileDescriptor;
@@ -66,8 +66,8 @@ public class WallpaperLocalColorExtractor {
private final List<RectF> mPendingRegions = new ArrayList<>();
private final Set<RectF> mProcessedRegions = new ArraySet<>();
- @Background
- private final Executor mBackgroundExecutor;
+ @LongRunning
+ private final Executor mLongExecutor;
private final WallpaperLocalColorExtractorCallback mWallpaperLocalColorExtractorCallback;
@@ -101,13 +101,13 @@ public class WallpaperLocalColorExtractor {
/**
* Creates a new color extractor.
- * @param backgroundExecutor the executor on which the color extraction will be performed
+ * @param longExecutor the executor on which the color extraction will be performed
* @param wallpaperLocalColorExtractorCallback an interface to handle the callbacks from
* the color extractor.
*/
- public WallpaperLocalColorExtractor(@Background Executor backgroundExecutor,
+ public WallpaperLocalColorExtractor(@LongRunning Executor longExecutor,
WallpaperLocalColorExtractorCallback wallpaperLocalColorExtractorCallback) {
- mBackgroundExecutor = backgroundExecutor;
+ mLongExecutor = longExecutor;
mWallpaperLocalColorExtractorCallback = wallpaperLocalColorExtractorCallback;
}
@@ -117,7 +117,7 @@ public class WallpaperLocalColorExtractor {
* not recomputed.
*/
public void setDisplayDimensions(int displayWidth, int displayHeight) {
- mBackgroundExecutor.execute(() ->
+ mLongExecutor.execute(() ->
setDisplayDimensionsSynchronized(displayWidth, displayHeight));
}
@@ -144,7 +144,7 @@ public class WallpaperLocalColorExtractor {
* @param bitmap the new wallpaper
*/
public void onBitmapChanged(@NonNull Bitmap bitmap) {
- mBackgroundExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
+ mLongExecutor.execute(() -> onBitmapChangedSynchronized(bitmap));
}
private void onBitmapChangedSynchronized(@NonNull Bitmap bitmap) {
@@ -167,7 +167,7 @@ public class WallpaperLocalColorExtractor {
* @param pages the total number of pages of the launcher
*/
public void onPageChanged(int pages) {
- mBackgroundExecutor.execute(() -> onPageChangedSynchronized(pages));
+ mLongExecutor.execute(() -> onPageChangedSynchronized(pages));
}
private void onPageChangedSynchronized(int pages) {
@@ -194,7 +194,7 @@ public class WallpaperLocalColorExtractor {
*/
public void addLocalColorsAreas(@NonNull List<RectF> regions) {
if (regions.size() > 0) {
- mBackgroundExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
+ mLongExecutor.execute(() -> addLocalColorsAreasSynchronized(regions));
} else {
Log.w(TAG, "Attempt to add colors with an empty list");
}
@@ -218,7 +218,7 @@ public class WallpaperLocalColorExtractor {
* @param regions The areas of interest in our wallpaper (in screen pixel coordinates)
*/
public void removeLocalColorAreas(@NonNull List<RectF> regions) {
- mBackgroundExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
+ mLongExecutor.execute(() -> removeLocalColorAreasSynchronized(regions));
}
private void removeLocalColorAreasSynchronized(@NonNull List<RectF> regions) {
@@ -236,7 +236,7 @@ public class WallpaperLocalColorExtractor {
* Clean up the memory (in particular, the mini bitmap) used by this class.
*/
public void cleanUp() {
- mBackgroundExecutor.execute(this::cleanUpSynchronized);
+ mLongExecutor.execute(this::cleanUpSynchronized);
}
private void cleanUpSynchronized() {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 9600fd88a2a3..08f7eaee3011 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -25,7 +25,6 @@ import static android.service.notification.NotificationListenerService.REASON_GR
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static com.android.systemui.flags.Flags.WM_BUBBLE_BAR;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -364,7 +363,6 @@ public class BubblesManager {
});
}
};
- mBubbles.setBubbleBarEnabled(featureFlags.isEnabled(WM_BUBBLE_BAR));
mBubbles.setSysuiProxy(mSysuiProxy);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index bd77c327e765..86ba30cee7a3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -2210,6 +2210,32 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testPostureChangeToUnsupported_stopsFaceListeningState() {
+ // GIVEN device is listening for face
+ mKeyguardUpdateMonitor.mConfigFaceAuthSupportedPosture = DEVICE_POSTURE_CLOSED;
+ deviceInPostureStateClosed();
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+ mTestableLooper.processAllMessages();
+ keyguardIsVisible();
+
+ verifyFaceAuthenticateCall();
+
+ final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+ mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+ KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+ mKeyguardUpdateMonitor.registerCallback(callback);
+
+ // WHEN device is opened
+ deviceInPostureStateOpened();
+ mTestableLooper.processAllMessages();
+
+ // THEN face listening is stopped.
+ verify(faceCancel).cancel();
+ verify(callback).onBiometricRunningStateChanged(
+ eq(false), eq(BiometricSourceType.FACE));
+ }
+
+ @Test
public void testShouldListenForFace_withLockedDown_returnsFalse()
throws RemoteException {
keyguardNotGoingAway();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 47c91911e52a..52a70ee9cce2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -201,6 +201,13 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase {
assertThat(magnifierMediumButton.isSelected()).isTrue();
}
+ @Test
+ public void showSettingPanel_focusOnThePanel() {
+ mWindowMagnificationSettings.showSettingPanel();
+
+ assertThat(mSettingView.isFocused()).isTrue();
+ }
+
private <T extends View> T getInternalView(@IdRes int idRes) {
T view = mSettingView.findViewById(idRes);
assertNotNull(view);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index b765ab3c5eac..a245c01d74de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -25,7 +25,9 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -109,6 +111,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
udfpsControllerProvider,
statusBarStateController,
featureFlags,
+ KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
rippleView
)
controller.init()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
index 35039026fe9e..9d16185e75c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
@@ -45,8 +45,8 @@ import org.mockito.MockitoAnnotations;
public class BroadcastDialogTest extends SysuiTestCase {
private static final String CURRENT_BROADCAST_APP = "Music";
- private static final String SWITCH_APP = "Files by Google";
- private static final String TEST_PACKAGE = "com.google.android.apps.nbu.files";
+ private static final String SWITCH_APP = "System UI";
+ private static final String TEST_PACKAGE = "com.android.systemui";
private BroadcastDialog mBroadcastDialog;
private View mDialogView;
private TextView mTitle;
@@ -59,6 +59,7 @@ public class BroadcastDialogTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class),
CURRENT_BROADCAST_APP, TEST_PACKAGE, mock(UiEventLogger.class));
+
mBroadcastDialog.show();
mDialogView = mBroadcastDialog.mDialogView;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
index faa5db4327a6..ab6d5b771d5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/DistanceClassifierTest.java
@@ -94,7 +94,9 @@ public class DistanceClassifierTest extends ClassifierTest {
mClassifier.onTouchEvent(appendMoveEvent(1, 16, 3));
mClassifier.onTouchEvent(appendMoveEvent(1, 17, 300));
mClassifier.onTouchEvent(appendMoveEvent(1, 18, 301));
- mClassifier.onTouchEvent(appendUpEvent(1, 19, 501));
+ mClassifier.onTouchEvent(appendMoveEvent(1, 19, 501));
+ mClassifier.onTouchEvent(appendUpEvent(1, 19, 501)); //event will be dropped
+
assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 2edc3d361316..8eadadff1ca5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -75,16 +75,17 @@ public class FalsingDataProviderTest extends ClassifierTest {
}
@Test
- public void test_trackMotionEvents() {
+ public void test_trackMotionEvents_dropUpEvent() {
mDataProvider.onMotionEvent(appendDownEvent(2, 9));
mDataProvider.onMotionEvent(appendMoveEvent(4, 7));
- mDataProvider.onMotionEvent(appendUpEvent(6, 5));
+ mDataProvider.onMotionEvent(appendMoveEvent(6, 5));
+ mDataProvider.onMotionEvent(appendUpEvent(0, 0)); // event will be dropped
List<MotionEvent> motionEventList = mDataProvider.getRecentMotionEvents();
assertThat(motionEventList.size()).isEqualTo(3);
assertThat(motionEventList.get(0).getActionMasked()).isEqualTo(MotionEvent.ACTION_DOWN);
assertThat(motionEventList.get(1).getActionMasked()).isEqualTo(MotionEvent.ACTION_MOVE);
- assertThat(motionEventList.get(2).getActionMasked()).isEqualTo(MotionEvent.ACTION_UP);
+ assertThat(motionEventList.get(2).getActionMasked()).isEqualTo(MotionEvent.ACTION_MOVE);
assertThat(motionEventList.get(0).getEventTime()).isEqualTo(1L);
assertThat(motionEventList.get(1).getEventTime()).isEqualTo(2L);
assertThat(motionEventList.get(2).getEventTime()).isEqualTo(3L);
@@ -97,6 +98,28 @@ public class FalsingDataProviderTest extends ClassifierTest {
}
@Test
+ public void test_trackMotionEvents_keepUpEvent() {
+ mDataProvider.onMotionEvent(appendDownEvent(2, 9));
+ mDataProvider.onMotionEvent(appendMoveEvent(4, 7));
+ mDataProvider.onMotionEvent(appendUpEvent(0, 0, 100));
+ List<MotionEvent> motionEventList = mDataProvider.getRecentMotionEvents();
+
+ assertThat(motionEventList.size()).isEqualTo(3);
+ assertThat(motionEventList.get(0).getActionMasked()).isEqualTo(MotionEvent.ACTION_DOWN);
+ assertThat(motionEventList.get(1).getActionMasked()).isEqualTo(MotionEvent.ACTION_MOVE);
+ assertThat(motionEventList.get(2).getActionMasked()).isEqualTo(MotionEvent.ACTION_UP);
+ assertThat(motionEventList.get(0).getEventTime()).isEqualTo(1L);
+ assertThat(motionEventList.get(1).getEventTime()).isEqualTo(2L);
+ assertThat(motionEventList.get(2).getEventTime()).isEqualTo(100);
+ assertThat(motionEventList.get(0).getX()).isEqualTo(2f);
+ assertThat(motionEventList.get(1).getX()).isEqualTo(4f);
+ assertThat(motionEventList.get(2).getX()).isEqualTo(0f);
+ assertThat(motionEventList.get(0).getY()).isEqualTo(9f);
+ assertThat(motionEventList.get(1).getY()).isEqualTo(7f);
+ assertThat(motionEventList.get(2).getY()).isEqualTo(0f);
+ }
+
+ @Test
public void test_trackRecentMotionEvents() {
mDataProvider.onMotionEvent(appendDownEvent(2, 9, 1));
mDataProvider.onMotionEvent(appendMoveEvent(4, 7, 800));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
index c343c20398e9..ae2b8bbb4ce6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/ZigZagClassifierTest.java
@@ -68,6 +68,15 @@ public class ZigZagClassifierTest extends ClassifierTest {
}
@Test
+ public void testPass_dropClosingUpEvent() {
+ appendMoveEvent(0, 0);
+ appendMoveEvent(0, 100);
+ appendMoveEvent(0, 200);
+ appendUpEvent(0, 180); // this event would push us over the maxDevianceY
+ assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
+ }
+
+ @Test
public void testPass_fewTouchesHorizontal() {
assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
appendMoveEvent(0, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 2099281d694a..ffd75fb9bbc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.RemoteAction;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -101,6 +102,9 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
+ @Captor
+ private ArgumentCaptor<AnimatorListenerAdapter> mAnimatorArgumentCaptor;
+
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
@@ -446,7 +450,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
.thenReturn(true);
- when(mClipboardUtils.getAction(any(CharSequence.class), any(TextLinks.class), anyString()))
+ when(mClipboardUtils.getAction(any(TextLinks.class), anyString()))
.thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
@Override
@@ -478,12 +482,16 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
when(mClipboardOverlayWindow.getWindowInsets()).thenReturn(
getImeInsets(new Rect(0, 0, 0, 1)));
mOverlayController.setClipData(mSampleClipData, "");
+ Animator mockFadeoutAnimator = Mockito.mock(Animator.class);
+ when(mClipboardOverlayView.getMinimizedFadeoutAnimation()).thenReturn(mockFadeoutAnimator);
verify(mClipboardOverlayView).setMinimized(true);
verify(mClipboardOverlayView, never()).setMinimized(false);
verify(mClipboardOverlayView, never()).showTextPreview(any(), anyBoolean());
mCallbacks.onMinimizedViewTapped();
+ verify(mockFadeoutAnimator).addListener(mAnimatorArgumentCaptor.capture());
+ mAnimatorArgumentCaptor.getValue().onAnimationEnd(mockFadeoutAnimator);
verify(mClipboardOverlayView).setMinimized(false);
verify(mClipboardOverlayView).showTextPreview("Test Item", false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
index aea6be3d468b..3d8f04e08825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.when;
@@ -77,6 +78,74 @@ public class ClipboardOverlayUtilsTest extends SysuiTestCase {
@Test
public void test_getAction_noLinks_returnsEmptyOptional() {
+ Optional<RemoteAction> action =
+ mClipboardUtils.getAction(Mockito.mock(TextLinks.class), "abc");
+
+ assertTrue(action.isEmpty());
+ }
+
+ @Test
+ public void test_getAction_returnsFirstLink() {
+ TextLinks links = getFakeTextLinksBuilder().build();
+ RemoteAction actionA = constructRemoteAction("abc");
+ RemoteAction actionB = constructRemoteAction("def");
+ TextClassification classificationA = Mockito.mock(TextClassification.class);
+ when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
+ TextClassification classificationB = Mockito.mock(TextClassification.class);
+ when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
+ when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
+ classificationA, classificationB);
+
+ RemoteAction result = mClipboardUtils.getAction(links, "test").orElse(null);
+
+ assertEquals(actionA, result);
+ }
+
+ @Test
+ public void test_getAction_skipsMatchingComponent() {
+ TextLinks links = getFakeTextLinksBuilder().build();
+ RemoteAction actionA = constructRemoteAction("abc");
+ RemoteAction actionB = constructRemoteAction("def");
+ TextClassification classificationA = Mockito.mock(TextClassification.class);
+ when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
+ TextClassification classificationB = Mockito.mock(TextClassification.class);
+ when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
+ when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
+ classificationA, classificationB);
+
+ RemoteAction result = mClipboardUtils.getAction(links, "abc").orElse(null);
+
+ assertEquals(actionB, result);
+ }
+
+ @Test
+ public void test_getAction_skipsShortEntity() {
+ TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
+ final Map<String, Float> scores = new ArrayMap<>();
+ scores.put(TextClassifier.TYPE_EMAIL, 1f);
+ textLinks.addLink(20, 22, scores);
+ textLinks.addLink(0, 22, scores);
+
+ RemoteAction actionA = constructRemoteAction("abc");
+ RemoteAction actionB = constructRemoteAction("def");
+ TextClassification classificationA = Mockito.mock(TextClassification.class);
+ when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
+ TextClassification classificationB = Mockito.mock(TextClassification.class);
+ when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
+ when(mTextClassifier.classifyText(anyString(), eq(20), eq(22), isNull())).thenReturn(
+ classificationA);
+ when(mTextClassifier.classifyText(anyString(), eq(0), eq(22), isNull())).thenReturn(
+ classificationB);
+
+ RemoteAction result = mClipboardUtils.getAction(textLinks.build(), "test").orElse(null);
+
+ assertEquals(actionB, result);
+ }
+
+ // TODO(b/267162944): Next four tests (marked "legacy") are obsolete once
+ // CLIPBOARD_MINIMIZED_LAYOUT flag is released and removed
+ @Test
+ public void test_getAction_noLinks_returnsEmptyOptional_legacy() {
ClipData.Item item = new ClipData.Item("no text links");
item.setTextLinks(Mockito.mock(TextLinks.class));
@@ -86,8 +155,8 @@ public class ClipboardOverlayUtilsTest extends SysuiTestCase {
}
@Test
- public void test_getAction_returnsFirstLink() {
- when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinks());
+ public void test_getAction_returnsFirstLink_legacy() {
+ when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
when(mClipDataItem.getText()).thenReturn("");
RemoteAction actionA = constructRemoteAction("abc");
RemoteAction actionB = constructRemoteAction("def");
@@ -98,14 +167,14 @@ public class ClipboardOverlayUtilsTest extends SysuiTestCase {
when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
classificationA, classificationB);
- RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "def").orElse(null);
+ RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
assertEquals(actionA, result);
}
@Test
- public void test_getAction_skipsMatchingComponent() {
- when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinks());
+ public void test_getAction_skipsMatchingComponent_legacy() {
+ when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
when(mClipDataItem.getText()).thenReturn("");
RemoteAction actionA = constructRemoteAction("abc");
RemoteAction actionB = constructRemoteAction("def");
@@ -122,6 +191,33 @@ public class ClipboardOverlayUtilsTest extends SysuiTestCase {
}
@Test
+ public void test_getAction_skipsShortEntity_legacy() {
+ TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
+ final Map<String, Float> scores = new ArrayMap<>();
+ scores.put(TextClassifier.TYPE_EMAIL, 1f);
+ textLinks.addLink(20, 22, scores);
+ textLinks.addLink(0, 22, scores);
+
+ when(mClipDataItem.getTextLinks()).thenReturn(textLinks.build());
+ when(mClipDataItem.getText()).thenReturn(textLinks.build().getText());
+
+ RemoteAction actionA = constructRemoteAction("abc");
+ RemoteAction actionB = constructRemoteAction("def");
+ TextClassification classificationA = Mockito.mock(TextClassification.class);
+ when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
+ TextClassification classificationB = Mockito.mock(TextClassification.class);
+ when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
+ when(mTextClassifier.classifyText(anyString(), eq(20), eq(22), isNull())).thenReturn(
+ classificationA);
+ when(mTextClassifier.classifyText(anyString(), eq(0), eq(22), isNull())).thenReturn(
+ classificationB);
+
+ RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
+
+ assertEquals(actionB, result);
+ }
+
+ @Test
public void test_extra_withPackage_returnsTrue() {
PersistableBundle b = new PersistableBundle();
b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true);
@@ -184,12 +280,12 @@ public class ClipboardOverlayUtilsTest extends SysuiTestCase {
return action;
}
- private static TextLinks getFakeTextLinks() {
- TextLinks.Builder textLinks = new TextLinks.Builder("test");
+ private static TextLinks.Builder getFakeTextLinksBuilder() {
+ TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
final Map<String, Float> scores = new ArrayMap<>();
scores.put(TextClassifier.TYPE_EMAIL, 1f);
- textLinks.addLink(0, 0, scores);
- textLinks.addLink(0, 0, scores);
- return textLinks.build();
+ textLinks.addLink(0, 22, scores);
+ textLinks.addLink(0, 22, scores);
+ return textLinks;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
index eafe727ee7dc..afd9be5787c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/common/ui/view/SeekBarWithIconButtonsViewTest.java
@@ -107,4 +107,32 @@ public class SeekBarWithIconButtonsViewTest extends SysuiTestCase {
assertThat(mSeekbar.getProgress()).isEqualTo(0);
}
+
+ @Test
+ public void setProgressStateLabels_getExpectedStateDescriptionOnInitialization() {
+ String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
+ mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+ mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels);
+
+ final int currentProgress = mSeekbar.getProgress();
+ final CharSequence stateDescription = mSeekbar.getStateDescription();
+
+ assertThat(currentProgress).isEqualTo(1);
+ assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]);
+ }
+
+ @Test
+ public void setProgressStateLabels_progressChanged_getExpectedStateDescription() {
+ String[] stateLabels = new String[]{"1", "2", "3", "4", "5"};
+ mIconDiscreteSliderLinearLayout.setMax(stateLabels.length);
+ mIconDiscreteSliderLinearLayout.setProgressStateLabels(stateLabels);
+ mIconDiscreteSliderLinearLayout.setProgress(1);
+
+ final int currentProgress = mSeekbar.getProgress();
+ final CharSequence stateDescription = mSeekbar.getStateDescription();
+
+ assertThat(currentProgress).isEqualTo(1);
+ assertThat(stateDescription).isEqualTo(stateLabels[currentProgress]);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 6c23254941a8..0a9470617a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -1,6 +1,8 @@
package com.android.systemui.dreams
+import android.animation.Animator
import android.animation.AnimatorSet
+import android.animation.ValueAnimator
import android.testing.AndroidTestingRunner
import android.view.View
import androidx.test.filters.SmallTest
@@ -10,13 +12,16 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition
import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
@@ -71,6 +76,19 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
}
@Test
+ fun testExitAnimationUpdatesState() {
+ controller.startExitAnimations(animatorBuilder = { mockAnimator })
+
+ verify(stateController).setExitAnimationsRunning(true)
+
+ val captor = argumentCaptor<Animator.AnimatorListener>()
+ verify(mockAnimator).addListener(captor.capture())
+
+ captor.value.onAnimationEnd(mockAnimator)
+ verify(stateController).setExitAnimationsRunning(false)
+ }
+
+ @Test
fun testWakeUpCallsExecutor() {
val mockExecutor: DelayableExecutor = mock()
val mockCallback: Runnable = mock()
@@ -87,7 +105,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
fun testWakeUpAfterStartWillCancel() {
val mockStartAnimator: AnimatorSet = mock()
- controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
+ controller.startEntryAnimations(false, animatorBuilder = { mockStartAnimator })
verify(mockStartAnimator, never()).cancel()
@@ -100,4 +118,50 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() {
// animator.
verify(mockStartAnimator, times(1)).cancel()
}
+
+ @Test
+ fun testEntryAnimations_translatesUpwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ false,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
+
+ @Test
+ fun testEntryAnimations_translatesDownwards() {
+ val mockStartAnimator: AnimatorSet = mock()
+
+ controller.startEntryAnimations(
+ /* downwards= */ true,
+ animatorBuilder = { mockStartAnimator }
+ )
+
+ val animatorCaptor = ArgumentCaptor.forClass(Animator::class.java)
+ verify(mockStartAnimator).playTogether(animatorCaptor.capture())
+
+ // Check if there's a ValueAnimator starting at the expected Y distance.
+ val animators: List<ValueAnimator> = animatorCaptor.allValues as List<ValueAnimator>
+ assertTrue(
+ animators.any {
+ // Call setCurrentFraction so the animated value jumps to the initial value.
+ it.setCurrentFraction(0f)
+ it.animatedValue == -DREAM_IN_TRANSLATION_Y_DISTANCE.toFloat()
+ }
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 6b095ffd3977..2a72e7d85d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.dreams;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -33,6 +34,7 @@ import android.view.ViewTreeObserver;
import androidx.test.filters.SmallTest;
+import com.android.dream.lowlight.LowLightTransitionCoordinator;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
@@ -65,6 +67,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
@Mock
+ LowLightTransitionCoordinator mLowLightTransitionCoordinator;
+
+ @Mock
DreamOverlayContainerView mDreamOverlayContainerView;
@Mock
@@ -109,6 +114,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mComplicationHostViewController,
mDreamOverlayContentView,
mDreamOverlayStatusBarViewController,
+ mLowLightTransitionCoordinator,
mBlurUtils,
mHandler,
mResources,
@@ -200,7 +206,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
- verify(mAnimationsController).startEntryAnimations();
+ verify(mAnimationsController).startEntryAnimations(false);
verify(mAnimationsController, never()).cancelAnimations();
}
@@ -210,11 +216,11 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
- verify(mAnimationsController, never()).startEntryAnimations();
+ verify(mAnimationsController, never()).startEntryAnimations(anyBoolean());
}
@Test
- public void testSkipEntryAnimationsWhenExitingLowLight() {
+ public void testDownwardEntryAnimationsWhenExitingLowLight() {
ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
when(mStateController.isLowLightActive()).thenReturn(false);
@@ -230,8 +236,14 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mController.onViewAttached();
// Entry animations should be started then immediately ended to skip to the end.
- verify(mAnimationsController).startEntryAnimations();
- verify(mAnimationsController).endAnimations();
+ verify(mAnimationsController).startEntryAnimations(true);
+ }
+
+ @Test
+ public void testStartsExitAnimationsBeforeEnteringLowLight() {
+ mController.onBeforeEnterLowLight();
+
+ verify(mAnimationsController).startExitAnimations();
}
@Test
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 dcd8736711f6..068852de7a43 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
@@ -22,6 +22,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.View;
@@ -33,6 +35,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -96,6 +100,10 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
private ComplicationHostViewController mController;
+ private SecureSettings mSecureSettings;
+
+ private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -108,12 +116,17 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+ mSecureSettings = new FakeSettings();
+ mSecureSettings.putFloatForUser(
+ Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f, CURRENT_USER_ID);
+
mController = new ComplicationHostViewController(
mComplicationHostView,
mLayoutEngine,
mDreamOverlayStateController,
mLifecycleOwner,
- mViewModel);
+ mViewModel,
+ mSecureSettings);
mController.init();
}
@@ -188,6 +201,23 @@ public class ComplicationHostViewControllerTest extends SysuiTestCase {
verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
}
+ @Test
+ public void testAnimationsDisabled_ComplicationsNeverSetToInvisible() {
+ //Disable animations
+ mController.mIsAnimationEnabled = false;
+
+ final Observer<Collection<ComplicationViewModel>> observer =
+ captureComplicationViewModelsObserver();
+
+ // Add a complication before entry animations are finished.
+ final HashSet<ComplicationViewModel> complications = new HashSet<>(
+ Collections.singletonList(mComplicationViewModel));
+ observer.onChanged(complications);
+
+ // The complication view should not be set to invisible.
+ verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+ }
+
private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
mObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 3a168d4e234b..d6dbd730368e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -450,6 +450,15 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
swipeToPosition(0f, Direction.DOWN, 0);
}
+ @Test
+ public void testTouchSessionOnRemovedCalledTwice() {
+ mTouchHandler.onSessionStart(mTouchSession);
+ ArgumentCaptor<DreamTouchHandler.TouchSession.Callback> onRemovedCallbackCaptor =
+ ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.Callback.class);
+ verify(mTouchSession).registerCallback(onRemovedCallbackCaptor.capture());
+ onRemovedCallbackCaptor.getValue().onRemoved();
+ onRemovedCallbackCaptor.getValue().onRemoved();
+ }
private void swipeToPosition(float percent, Direction direction, float velocityY) {
Mockito.clearInvocations(mTouchSession);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
new file mode 100644
index 000000000000..ec94cdec78f0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.keyboard.backlight.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyboard.shared.model.BacklightModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyboardBacklightInteractorTest : SysuiTestCase() {
+
+ private val keyboardRepository = FakeKeyboardRepository()
+ private lateinit var underTest: KeyboardBacklightInteractor
+
+ @Before
+ fun setUp() {
+ underTest = KeyboardBacklightInteractor(keyboardRepository)
+ }
+
+ @Test
+ fun emitsNull_whenKeyboardJustConnected() = runTest {
+ val latest by collectLastValue(underTest.backlight)
+ keyboardRepository.setKeyboardConnected(true)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun emitsBacklight_whenKeyboardConnectedAndBacklightChanged() = runTest {
+ keyboardRepository.setKeyboardConnected(true)
+ keyboardRepository.setBacklight(BacklightModel(1, 5))
+
+ assertThat(underTest.backlight.first()).isEqualTo(BacklightModel(1, 5))
+ }
+
+ @Test
+ fun emitsNull_afterKeyboardDisconnecting() = runTest {
+ val latest by collectLastValue(underTest.backlight)
+ keyboardRepository.setKeyboardConnected(true)
+ keyboardRepository.setBacklight(BacklightModel(1, 5))
+
+ keyboardRepository.setKeyboardConnected(false)
+
+ assertThat(latest).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt
new file mode 100644
index 000000000000..ec05d10b793c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/viewmodel/BacklightDialogViewModelTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.keyboard.backlight.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.backlight.domain.interactor.KeyboardBacklightInteractor
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyboard.shared.model.BacklightModel
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class BacklightDialogViewModelTest : SysuiTestCase() {
+
+ private val keyboardRepository = FakeKeyboardRepository()
+ private lateinit var underTest: BacklightDialogViewModel
+ @Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
+ private val timeoutMillis = 3000L
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(accessibilityManagerWrapper.getRecommendedTimeoutMillis(any(), any()))
+ .thenReturn(timeoutMillis.toInt())
+ underTest =
+ BacklightDialogViewModel(
+ KeyboardBacklightInteractor(keyboardRepository),
+ accessibilityManagerWrapper
+ )
+ keyboardRepository.setKeyboardConnected(true)
+ }
+
+ @Test
+ fun emitsViewModel_whenBacklightChanged() = runTest {
+ keyboardRepository.setBacklight(BacklightModel(1, 5))
+
+ assertThat(underTest.dialogContent.first()).isEqualTo(BacklightDialogContentViewModel(1, 5))
+ }
+
+ @Test
+ fun emitsNull_afterTimeout() = runTest {
+ val latest by collectLastValue(underTest.dialogContent)
+ keyboardRepository.setBacklight(BacklightModel(1, 5))
+
+ assertThat(latest).isEqualTo(BacklightDialogContentViewModel(1, 5))
+ advanceTimeBy(timeoutMillis + 1)
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun emitsNull_after5secDelay_fromLastBacklightChange() = runTest {
+ val latest by collectLastValue(underTest.dialogContent)
+ keyboardRepository.setKeyboardConnected(true)
+
+ keyboardRepository.setBacklight(BacklightModel(1, 5))
+ assertThat(latest).isEqualTo(BacklightDialogContentViewModel(1, 5))
+
+ advanceTimeBy(timeoutMillis * 2 / 3)
+ // timeout yet to pass, no new emission
+ keyboardRepository.setBacklight(BacklightModel(2, 5))
+ assertThat(latest).isEqualTo(BacklightDialogContentViewModel(2, 5))
+
+ advanceTimeBy(timeoutMillis * 2 / 3)
+ // timeout refreshed because of last `setBacklight`, still content present
+ assertThat(latest).isEqualTo(BacklightDialogContentViewModel(2, 5))
+
+ advanceTimeBy(timeoutMillis * 2 / 3)
+ // finally timeout reached and null emitted
+ assertThat(latest).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index a4e5bcaecde4..984f4be0ae97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
@@ -91,6 +92,7 @@ class CustomizationProviderTest : SysuiTestCase() {
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
@@ -184,6 +186,7 @@ class CustomizationProviderTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ logger = logger,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 0469e77ca991..0e6f8d4e0720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -219,6 +219,29 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isKeyguardUnlocked() =
+ runTest(UnconfinedTestDispatcher()) {
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ var latest: Boolean? = null
+ val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest).isFalse()
+
+ val captor = argumentCaptor<KeyguardStateController.Callback>()
+ verify(keyguardStateController).addCallback(captor.capture())
+
+ whenever(keyguardStateController.isUnlocked).thenReturn(true)
+ captor.value.onUnlockedChanged()
+ assertThat(latest).isTrue()
+
+ whenever(keyguardStateController.isUnlocked).thenReturn(false)
+ captor.value.onUnlockedChanged()
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun isDozing() =
runTest(UnconfinedTestDispatcher()) {
var latest: Boolean? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 84ec125bfa55..46c623a7d3c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -42,6 +42,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
@@ -225,6 +226,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var testScope: TestScope
@@ -331,6 +333,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ logger = logger,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
)
@@ -360,10 +363,11 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
- underTest.onQuickAffordanceTriggered(
- configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
- expandable = expandable,
- )
+ underTest.onQuickAffordanceTriggered(
+ configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
+ expandable = expandable,
+ slotId = "",
+ )
if (startActivity) {
if (needsToUnlockFirst) {
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..cd579dbc132e 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
@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAff
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -80,6 +81,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -186,6 +188,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ logger = logger,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
)
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 ae7a928cdb2c..fe9098fa5c25 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
@@ -19,6 +19,8 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
import com.android.systemui.flags.FakeFeatureFlags
@@ -40,6 +42,7 @@ import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelChildren
@@ -51,6 +54,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -77,6 +82,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
// Used to verify transition requests for test output
@Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
@Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
@@ -102,6 +108,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
transitionRepository = KeyguardTransitionRepositoryImpl()
runner = KeyguardTransitionRunner(transitionRepository)
+ whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
+
val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
fromLockscreenTransitionInteractor =
FromLockscreenTransitionInteractor(
@@ -173,16 +181,17 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
keyguardInteractor = createKeyguardInteractor(featureFlags),
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+ keyguardSecurityModel = keyguardSecurityModel,
)
fromPrimaryBouncerTransitionInteractor.start()
}
@Test
- fun `DREAMING to LOCKSCREEN - dreaming state changes first`() =
+ fun `DREAMING to LOCKSCREEN`() =
testScope.runTest {
- // GIVEN a device is dreaming and occluded
+ // GIVEN a device is dreaming
keyguardRepository.setDreamingWithOverlay(true)
- keyguardRepository.setKeyguardOccluded(true)
+ keyguardRepository.setWakefulnessModel(startingToWake())
runCurrent()
// GIVEN a prior transition has run to DREAMING
@@ -215,56 +224,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
- }
- // THEN a transition to BOUNCER should occur
- assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
- assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
- assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
- assertThat(info.animator).isNotNull()
-
- coroutineContext.cancelChildren()
- }
-
- @Test
- fun `DREAMING to LOCKSCREEN - occluded state changes first`() =
- testScope.runTest {
- // GIVEN a device is dreaming and occluded
- keyguardRepository.setDreamingWithOverlay(true)
- keyguardRepository.setKeyguardOccluded(true)
- runCurrent()
-
- // GIVEN a prior transition has run to DREAMING
- runner.startTransition(
- testScope,
- TransitionInfo(
- ownerName = "",
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.DREAMING,
- animator =
- ValueAnimator().apply {
- duration = 10
- interpolator = Interpolators.LINEAR
- },
- )
- )
- runCurrent()
- reset(mockTransitionRepository)
-
- // WHEN doze is complete
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- // AND occluded has stopped
- keyguardRepository.setKeyguardOccluded(false)
- advanceUntilIdle()
- // AND then dreaming has stopped
- keyguardRepository.setDreamingWithOverlay(false)
- advanceUntilIdle()
-
- val info =
- withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
@@ -304,7 +264,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -345,7 +305,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -386,7 +346,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
@@ -427,7 +387,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -468,7 +428,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
@@ -505,7 +465,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -542,7 +502,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromDozingTransitionInteractor")
@@ -583,7 +543,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -624,7 +584,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -661,7 +621,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -677,6 +637,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
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)
)
@@ -704,7 +665,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DREAMING should occur
assertThat(info.ownerName).isEqualTo("FromGoneTransitionInteractor")
@@ -741,7 +702,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to PRIMARY_BOUNCER should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -784,7 +745,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -828,7 +789,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -870,7 +831,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromAlternateBouncerTransitionInteractor")
@@ -912,7 +873,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to AOD should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -954,7 +915,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to DOZING should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
@@ -995,7 +956,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
val info =
withArgCaptor<TransitionInfo> {
- verify(mockTransitionRepository).startTransition(capture())
+ verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
}
// THEN a transition to LOCKSCREEN should occur
assertThat(info.ownerName).isEqualTo("FromPrimaryBouncerTransitionInteractor")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index c727b3a6cd10..bfc09d7c0379 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceIn
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
@@ -89,6 +90,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardBottomAreaViewModel
@@ -208,6 +210,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
featureFlags = featureFlags,
repository = { quickAffordanceRepository },
launchAnimator = launchAnimator,
+ logger = logger,
devicePolicyManager = devicePolicyManager,
backgroundDispatcher = testDispatcher,
),
@@ -230,6 +233,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -260,6 +264,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -272,6 +277,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
testConfig =
TestConfig(
isVisible = false,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
),
configKey = configKey,
)
@@ -299,6 +305,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
),
)
@@ -313,6 +320,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
canShowWhileLocked = false,
intent = Intent("action"),
isSelected = true,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
),
configKey = configKey,
)
@@ -341,6 +349,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
),
)
val configKey =
@@ -354,6 +363,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = icon,
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
),
)
@@ -368,6 +378,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
canShowWhileLocked = false,
intent = Intent("action"),
isDimmed = true,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
),
configKey = configKey,
)
@@ -387,6 +398,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
canShowWhileLocked = false,
intent =
null, // This will cause it to tell the system that the click was handled.
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -409,6 +421,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
val config =
TestConfig(
isVisible = false,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -434,6 +447,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
setUpQuickAffordanceModel(
@@ -513,6 +527,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
isClickable = true,
icon = mock(),
canShowWhileLocked = true,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
)
assertThat(value()).isTrue()
@@ -524,6 +539,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
isClickable = true,
icon = mock(),
canShowWhileLocked = false,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
)
)
assertThat(value()).isTrue()
@@ -532,6 +548,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
testConfig =
TestConfig(
isVisible = false,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
)
assertThat(value()).isTrue()
@@ -540,6 +557,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
testConfig =
TestConfig(
isVisible = false,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
)
)
assertThat(value()).isFalse()
@@ -594,6 +612,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -626,6 +645,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -656,6 +676,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -684,6 +705,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
icon = mock(),
canShowWhileLocked = false,
intent = Intent("action"),
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
)
val configKey =
setUpQuickAffordanceModel(
@@ -748,12 +770,14 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
+ assertThat(viewModel.slotId).isEqualTo(testConfig.slotId)
if (testConfig.isVisible) {
assertThat(viewModel.icon).isEqualTo(testConfig.icon)
viewModel.onClicked.invoke(
KeyguardQuickAffordanceViewModel.OnClickedParameters(
configKey = configKey,
expandable = expandable,
+ slotId = viewModel.slotId,
)
)
if (testConfig.intent != null) {
@@ -775,6 +799,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
val intent: Intent? = null,
val isSelected: Boolean = false,
val isDimmed: Boolean = false,
+ val slotId: String = ""
) {
init {
check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
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
new file mode 100644
index 000000000000..2a91799741b7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -0,0 +1,109 @@
+/*
+ * 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.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+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.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
+ private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
+ private lateinit var repository: FakeKeyguardTransitionRepository
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ repository = FakeKeyguardTransitionRepository()
+ val interactor = KeyguardTransitionInteractor(repository)
+ underTest = PrimaryBouncerToGoneTransitionViewModel(interactor, statusBarStateController)
+ }
+
+ @Test
+ fun scrimBehindAlpha_leaveShadeOpen() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).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(1f) }
+
+ job.cancel()
+ }
+
+ @Test
+ fun scrimBehindAlpha_doNotLeaveShadeOpen() =
+ runTest(UnconfinedTestDispatcher()) {
+ val values = mutableListOf<Float>()
+
+ val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+
+ whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
+
+ 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).isIn(Range.closed(0f, 1f)) }
+ assertThat(values[3]).isEqualTo(0f)
+
+ job.cancel()
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = value,
+ transitionState = state,
+ ownerName = "PrimaryBouncerToGoneTransitionViewModelTest"
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 7f5707722b9c..e0ca90ec2c58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -18,7 +18,6 @@ package com.android.systemui.media.controls.ui
import android.app.PendingIntent
import android.content.res.ColorStateList
-import android.content.res.Configuration
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.MathUtils.abs
@@ -685,46 +684,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
}
@Test
- fun testOnConfigChanged_playersAreAddedBack() {
- mediaCarouselController.pageIndicator = pageIndicator
-
- listener.value.onMediaDataLoaded(
- "playing local",
- null,
- DATA.copy(
- active = true,
- isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
- )
- listener.value.onMediaDataLoaded(
- "paused local",
- null,
- DATA.copy(
- active = true,
- isPlaying = false,
- playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
- )
- runAllReady()
-
- val playersSize = MediaPlayerData.players().size
-
- configListener.value.onConfigChanged(Configuration())
- runAllReady()
-
- verify(pageIndicator).tintList =
- ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
- assertEquals(playersSize, MediaPlayerData.players().size)
- assertEquals(
- MediaPlayerData.getMediaPlayerIndex("playing local"),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
- )
- }
-
- @Test
fun testOnUiModeChanged_playersAreAddedBack() {
mediaCarouselController.pageIndicator = pageIndicator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 55a33b6636e0..fd353afff7c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -27,6 +27,7 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
+import android.graphics.Matrix
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
@@ -78,6 +79,8 @@ import com.android.systemui.media.controls.pipeline.EMPTY_SMARTSPACE_MEDIA_DATA
import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.monet.ColorScheme
+import com.android.systemui.monet.Style
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -214,6 +217,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var recSubtitleMock2: TextView
@Mock private lateinit var recSubtitleMock3: TextView
@Mock private lateinit var coverItem: ImageView
+ @Mock private lateinit var matrix: Matrix
private lateinit var coverItem1: ImageView
private lateinit var coverItem2: ImageView
private lateinit var coverItem3: ImageView
@@ -700,6 +704,46 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ fun addTwoPlayerGradients_differentStates() {
+ // Setup redArtwork and its color scheme.
+ val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val redCanvas = Canvas(redBmp)
+ redCanvas.drawColor(Color.RED)
+ val redArt = Icon.createWithBitmap(redBmp)
+ val redWallpaperColor = player.getWallpaperColor(redArt)
+ val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
+
+ // Setup greenArt and its color scheme.
+ val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val greenCanvas = Canvas(greenBmp)
+ greenCanvas.drawColor(Color.GREEN)
+ val greenArt = Icon.createWithBitmap(greenBmp)
+ val greenWallpaperColor = player.getWallpaperColor(greenArt)
+ val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
+
+ // Add gradient to both icons.
+ val redArtwork = player.addGradientToPlayerAlbum(redArt, redColorScheme, 10, 10)
+ val greenArtwork = player.addGradientToPlayerAlbum(greenArt, greenColorScheme, 10, 10)
+
+ // They should have different constant states as they have different gradient color.
+ assertThat(redArtwork.getDrawable(1).constantState)
+ .isNotEqualTo(greenArtwork.getDrawable(1).constantState)
+ }
+
+ @Test
+ fun getWallpaperColor_recycledBitmap_notCrashing() {
+ // Setup redArt icon.
+ val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val redArt = Icon.createWithBitmap(redBmp)
+
+ // Recycle bitmap of redArt icon.
+ redArt.bitmap.recycle()
+
+ // get wallpaperColor without illegal exception.
+ player.getWallpaperColor(redArt)
+ }
+
+ @Test
fun bind_seekBarDisabled_hasActions_seekBarVisibilityIsSetToInvisible() {
useRealConstraintSets()
@@ -2092,6 +2136,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
.thenReturn(listOf(recProgressBar1, recProgressBar2, recProgressBar3))
whenever(recommendationViewHolder.mediaSubtitles)
.thenReturn(listOf(recSubtitleMock1, recSubtitleMock2, recSubtitleMock3))
+ whenever(coverItem.imageMatrix).thenReturn(matrix)
val bmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bmp)
@@ -2127,6 +2172,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
verify(recCardTitle).setTextColor(any<Int>())
verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java))
verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java))
+ verify(coverItem, times(3)).imageMatrix = any()
}
@Test
@@ -2189,6 +2235,34 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
+ fun addTwoRecommendationGradients_differentStates() {
+ // Setup redArtwork and its color scheme.
+ val redBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val redCanvas = Canvas(redBmp)
+ redCanvas.drawColor(Color.RED)
+ val redArt = Icon.createWithBitmap(redBmp)
+ val redWallpaperColor = player.getWallpaperColor(redArt)
+ val redColorScheme = ColorScheme(redWallpaperColor, true, Style.CONTENT)
+
+ // Setup greenArt and its color scheme.
+ val greenBmp = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888)
+ val greenCanvas = Canvas(greenBmp)
+ greenCanvas.drawColor(Color.GREEN)
+ val greenArt = Icon.createWithBitmap(greenBmp)
+ val greenWallpaperColor = player.getWallpaperColor(greenArt)
+ val greenColorScheme = ColorScheme(greenWallpaperColor, true, Style.CONTENT)
+
+ // Add gradient to both icons.
+ val redArtwork = player.addGradientToRecommendationAlbum(redArt, redColorScheme, 10, 10)
+ val greenArtwork =
+ player.addGradientToRecommendationAlbum(greenArt, greenColorScheme, 10, 10)
+
+ // They should have different constant states as they have different gradient color.
+ assertThat(redArtwork.getDrawable(1).constantState)
+ .isNotEqualTo(greenArtwork.getDrawable(1).constantState)
+ }
+
+ @Test
fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
val semanticActions =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
index af91cdb1522c..0fac3db2dc1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt
@@ -16,9 +16,12 @@
package com.android.systemui.media.controls.ui
+import android.content.res.Configuration
+import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
@@ -58,6 +61,8 @@ class MediaViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mediaSubTitleWidgetState: WidgetState
@Mock private lateinit var mediaContainerWidgetState: WidgetState
@Mock private lateinit var mediaFlags: MediaFlags
+ @Mock private lateinit var expandedLayout: ConstraintSet
+ @Mock private lateinit var collapsedLayout: ConstraintSet
val delta = 0.1F
@@ -77,6 +82,19 @@ class MediaViewControllerTest : SysuiTestCase() {
}
@Test
+ fun testOrientationChanged_layoutsAreLoaded() {
+ mediaViewController.expandedLayout = expandedLayout
+ mediaViewController.collapsedLayout = collapsedLayout
+
+ val newConfig = Configuration()
+ newConfig.orientation = ORIENTATION_LANDSCAPE
+ configurationController.onConfigurationChanged(newConfig)
+
+ verify(expandedLayout).load(context, R.xml.media_session_expanded)
+ verify(collapsedLayout).load(context, R.xml.media_session_collapsed)
+ }
+
+ @Test
fun testObtainViewState_applySquishFraction_toPlayerTransitionViewState_height() {
mediaViewController.attach(player, MediaViewController.TYPE.PLAYER)
player.measureState =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 376b7cc70150..4efc30f0aee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -20,12 +20,17 @@ import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager
+import android.os.UserHandle
import android.os.UserManager
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -57,8 +62,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
@Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var userManager: UserManager
@Mock lateinit var eventLogger: NoteTaskEventLogger
- @Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ private val userTracker: UserTracker = FakeUserTracker()
private val noteTaskInfo = NoteTaskInfo(packageName = NOTES_PACKAGE_NAME, uid = NOTES_UID)
@@ -81,16 +86,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
private fun createNoteTaskController(
isEnabled: Boolean = true,
bubbles: Bubbles? = this.bubbles,
- keyguardManager: KeyguardManager? = this.keyguardManager,
- userManager: UserManager? = this.userManager,
): NoteTaskController =
NoteTaskController(
context = context,
resolver = resolver,
eventLogger = eventLogger,
optionalBubbles = Optional.ofNullable(bubbles),
- optionalUserManager = Optional.ofNullable(userManager),
- optionalKeyguardManager = Optional.ofNullable(keyguardManager),
+ userManager = userManager,
+ keyguardManager = keyguardManager,
isEnabled = isEnabled,
devicePolicyManager = devicePolicyManager,
userTracker = userTracker,
@@ -225,13 +228,19 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
val intentCaptor = argumentCaptor<Intent>()
- verify(context).startActivity(capture(intentCaptor))
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
@@ -259,7 +268,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
verifyZeroInteractions(eventLogger)
@@ -283,13 +292,22 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
val intentCaptor = argumentCaptor<Intent>()
- verify(context).startActivity(capture(intentCaptor))
+ val userCaptor = argumentCaptor<UserHandle>()
+ verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor))
+
+ (intentCaptor.value.flags and FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK
+
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK)
+ .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK)
+ assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT)
+ .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
+ assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
verifyZeroInteractions(bubbles)
}
@@ -306,28 +324,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
@Test
- fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
- createNoteTaskController(keyguardManager = null)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isInMultiWindowMode = false,
- )
-
- verifyZeroInteractions(context, bubbles, eventLogger)
- }
-
- @Test
- fun showNoteTask_userManagerIsNull_shouldDoNothing() {
- createNoteTaskController(userManager = null)
- .showNoteTask(
- entryPoint = NoteTaskEntryPoint.TAIL_BUTTON,
- isInMultiWindowMode = false,
- )
-
- verifyZeroInteractions(context, bubbles, eventLogger)
- }
-
- @Test
fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
whenever(resolver.resolveInfo(any(), any(), any())).thenReturn(null)
@@ -460,7 +456,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
}
@@ -487,7 +483,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
intentCaptor.value.let { intent ->
assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
- assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK)
+ assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
index 7f64f8a123e0..0c945dfa4b4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt
@@ -22,6 +22,8 @@ import android.content.pm.PackageManager
import android.test.suitebuilder.annotation.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -44,13 +46,14 @@ internal class NoteTaskInfoResolverTest : SysuiTestCase() {
@Mock lateinit var packageManager: PackageManager
@Mock lateinit var roleManager: RoleManager
+ private val userTracker: UserTracker = FakeUserTracker()
private lateinit var underTest: NoteTaskInfoResolver
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- underTest = NoteTaskInfoResolver(context, roleManager, packageManager)
+ underTest = NoteTaskInfoResolver(roleManager, packageManager, userTracker)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
index d6dfc8509165..ac106ef9bf51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -57,6 +57,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.`when`
+import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -351,4 +352,44 @@ class CustomTileTest : SysuiTestCase() {
.startPendingIntentDismissingKeyguard(
eq(pi), nullable(), nullable<ActivityLaunchAnimator.Controller>())
}
+
+ @Test
+ fun testActiveTileListensOnceAfterCreated() {
+ `when`(tileServiceManager.isActiveTile).thenReturn(true)
+
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.initialize()
+ tile.postStale()
+ testableLooper.processAllMessages()
+
+ verify(tileServiceManager).setBindRequested(true)
+ verify(tileService).onStartListening()
+ }
+
+ @Test
+ fun testActiveTileDoesntListenAfterFirstTime() {
+ `when`(tileServiceManager.isActiveTile).thenReturn(true)
+
+ val tile = CustomTile.create(customTileBuilder, TILE_SPEC, mContext)
+ tile.initialize()
+ // Make sure we have an icon in the tile because we don't have a default icon
+ // This should not be overridden by the retrieved tile that has null icon.
+ tile.qsTile.icon = mock(Icon::class.java)
+ `when`(tile.qsTile.icon.loadDrawable(any(Context::class.java)))
+ .thenReturn(mock(Drawable::class.java))
+
+ tile.postStale()
+ testableLooper.processAllMessages()
+
+ // postStale will set it to not listening after it's done
+ verify(tileService).onStopListening()
+
+ clearInvocations(tileServiceManager, tileService)
+
+ tile.setListening(Any(), true)
+ testableLooper.processAllMessages()
+
+ verify(tileServiceManager, never()).setBindRequested(true)
+ verify(tileService, never()).onStartListening()
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 515e1ee172ed..3c08d58cbb67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -18,14 +18,14 @@ package com.android.systemui.screenshot.appclips;
import static android.app.Activity.RESULT_OK;
-import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
-import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
+import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_ACCEPTED;
+import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_CANCELLED;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,36 +34,35 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.graphics.Bitmap;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.ResultReceiver;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.widget.ImageView;
-import androidx.lifecycle.MutableLiveData;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.intercepting.SingleActivityFactory;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.AppClipsActivity;
-import com.android.systemui.screenshot.AppClipsTrampolineActivity;
-import com.android.systemui.screenshot.AppClipsViewModel;
+import com.android.systemui.screenshot.ImageExporter;
import com.android.systemui.settings.UserTracker;
+import com.google.common.util.concurrent.Futures;
+
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.UUID;
+import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
@RunWith(AndroidTestingRunner.class)
@@ -78,18 +77,16 @@ public final class AppClipsActivityTest extends SysuiTestCase {
private static final String TEST_CALLING_PACKAGE = "test-calling-package";
@Mock
- private AppClipsViewModel.Factory mViewModelFactory;
+ private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+ @Mock
+ private ImageExporter mImageExporter;
@Mock
private PackageManager mPackageManager;
@Mock
private UserTracker mUserTracker;
@Mock
private UiEventLogger mUiEventLogger;
- @Mock
- private AppClipsViewModel mViewModel;
- private MutableLiveData<Bitmap> mScreenshotLiveData;
- private MutableLiveData<Uri> mResultLiveData;
private AppClipsActivity mActivity;
// Using the deprecated ActivityTestRule and SingleActivityFactory to help with injecting mocks.
@@ -97,8 +94,11 @@ public final class AppClipsActivityTest extends SysuiTestCase {
new SingleActivityFactory<>(AppClipsActivityTestable.class) {
@Override
protected AppClipsActivityTestable create(Intent unUsed) {
- return new AppClipsActivityTestable(mViewModelFactory, mPackageManager,
- mUserTracker, mUiEventLogger);
+ return new AppClipsActivityTestable(
+ new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper,
+ mImageExporter, getContext().getMainExecutor(),
+ directExecutor()), mPackageManager, mUserTracker,
+ mUiEventLogger);
}
};
@@ -110,29 +110,17 @@ public final class AppClipsActivityTest extends SysuiTestCase {
public void setUp() throws PackageManager.NameNotFoundException {
MockitoAnnotations.initMocks(this);
- mScreenshotLiveData = new MutableLiveData<>();
- mResultLiveData = new MutableLiveData<>();
- MutableLiveData<Integer> errorLiveData = new MutableLiveData<>();
-
- when(mViewModelFactory.create(any(Class.class))).thenReturn(mViewModel);
- when(mViewModel.getScreenshot()).thenReturn(mScreenshotLiveData);
- when(mViewModel.getResultLiveData()).thenReturn(mResultLiveData);
- when(mViewModel.getErrorLiveData()).thenReturn(errorLiveData);
when(mUserTracker.getUserId()).thenReturn(TEST_USER_ID);
-
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = TEST_UID;
when(mPackageManager.getApplicationInfoAsUser(eq(TEST_CALLING_PACKAGE),
any(ApplicationInfoFlags.class), eq(TEST_USER_ID))).thenReturn(applicationInfo);
- doAnswer(invocation -> {
- runOnMainThread(() -> mScreenshotLiveData.setValue(TEST_BITMAP));
- return null;
- }).when(mViewModel).performScreenshot();
- doAnswer(invocation -> {
- runOnMainThread(() -> mResultLiveData.setValue(TEST_URI));
- return null;
- }).when(mViewModel).saveScreenshotThenFinish(any(Drawable.class), any(Rect.class));
+ when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(TEST_BITMAP);
+ ImageExporter.Result result = new ImageExporter.Result();
+ result.uri = TEST_URI;
+ when(mImageExporter.export(any(Executor.class), any(UUID.class), any(Bitmap.class),
+ any(UserHandle.class))).thenReturn(Futures.immediateFuture(result));
}
@After
@@ -140,7 +128,6 @@ public final class AppClipsActivityTest extends SysuiTestCase {
mActivityRule.finishActivity();
}
- @Ignore("b/269403503")
@Test
public void appClipsLaunched_screenshotDisplayed() {
launchActivity();
@@ -148,7 +135,6 @@ public final class AppClipsActivityTest extends SysuiTestCase {
assertThat(((ImageView) mActivity.findViewById(R.id.preview)).getDrawable()).isNotNull();
}
- @Ignore("b/269403503")
@Test
public void screenshotDisplayed_userConsented_screenshotExportedSuccessfully() {
ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
@@ -168,7 +154,6 @@ public final class AppClipsActivityTest extends SysuiTestCase {
verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_ACCEPTED, TEST_UID, TEST_CALLING_PACKAGE);
}
- @Ignore("b/269403503")
@Test
public void screenshotDisplayed_userDeclined() {
ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index e40c49b56ac5..ad06dcc6f327 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -25,7 +25,8 @@ import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPP
import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
-import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
+import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
import static com.google.common.truth.Truth.assertThat;
@@ -59,8 +60,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.notetask.NoteTaskController;
-import com.android.systemui.screenshot.AppClipsTrampolineActivity;
-import com.android.systemui.screenshot.ScreenshotEvent;
import com.android.systemui.settings.UserTracker;
import com.android.wm.shell.bubbles.Bubbles;
@@ -262,8 +261,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
mActivityRule.launchActivity(mActivityIntent);
waitForIdleSync();
- verify(mUiEventLogger).log(ScreenshotEvent.SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID,
- TEST_CALLING_PACKAGE);
+ verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE);
}
private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index d5af7ce1d346..e7c3c0578627 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.screenshot;
+package com.android.systemui.screenshot.appclips;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
@@ -36,7 +36,7 @@ import android.os.UserHandle;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.screenshot.appclips.AppClipsCrossProcessHelper;
+import com.android.systemui.screenshot.ImageExporter;
import com.google.common.util.concurrent.Futures;
@@ -46,7 +46,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.time.ZonedDateTime;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -62,7 +61,7 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
@Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
@Mock private ImageExporter mImageExporter;
- private com.android.systemui.screenshot.AppClipsViewModel mViewModel;
+ private AppClipsViewModel mViewModel;
@Before
public void setUp() {
@@ -99,8 +98,8 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
@Test
public void saveScreenshot_throwsError_shouldUpdateErrorWithFailed() {
- when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
- ZonedDateTime.class), any(UserHandle.class))).thenReturn(
+ when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null),
+ any(UserHandle.class))).thenReturn(
Futures.immediateFailedFuture(new ExecutionException(new Throwable())));
mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
@@ -113,9 +112,9 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
@Test
public void saveScreenshot_failsSilently_shouldUpdateErrorWithFailed() {
- when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
- ZonedDateTime.class), any(UserHandle.class))).thenReturn(
- Futures.immediateFuture(new ImageExporter.Result()));
+ when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null),
+ any(UserHandle.class))).thenReturn(
+ Futures.immediateFuture(new ImageExporter.Result()));
mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
waitForIdleSync();
@@ -129,9 +128,8 @@ public final class AppClipsViewModelTest extends SysuiTestCase {
public void saveScreenshot_succeeds_shouldUpdateResultWithUri() {
ImageExporter.Result result = new ImageExporter.Result();
result.uri = FAKE_URI;
- when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
- ZonedDateTime.class), any(UserHandle.class))).thenReturn(
- Futures.immediateFuture(result));
+ when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null),
+ any(UserHandle.class))).thenReturn(Futures.immediateFuture(result));
mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
waitForIdleSync();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 0a401b09b6cf..82a57438052f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -65,48 +66,32 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var view: NotificationShadeWindowView
- @Mock
- private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var centralSurfaces: CentralSurfaces
- @Mock
- private lateinit var dockManager: DockManager
- @Mock
- private lateinit var notificationPanelViewController: NotificationPanelViewController
- @Mock
- private lateinit var notificationShadeDepthController: NotificationShadeDepthController
- @Mock
- private lateinit var notificationShadeWindowController: NotificationShadeWindowController
- @Mock
- private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock
- private lateinit var ambientState: AmbientState
- @Mock
- private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
- @Mock
- private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
- @Mock
- private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
- @Mock
- private lateinit var statusBarWindowStateController: StatusBarWindowStateController
+ @Mock private lateinit var view: NotificationShadeWindowView
+ @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var dockManager: DockManager
+ @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+ @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+ @Mock private lateinit var ambientState: AmbientState
+ @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
+ @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
+ @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
- @Mock
- private lateinit var lockIconViewController: LockIconViewController
- @Mock
- private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
- @Mock
- private lateinit var pulsingGestureListener: PulsingGestureListener
- @Mock
- private lateinit var notificationInsetsController: NotificationInsetsController
- @Mock
- private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ @Mock private lateinit var lockIconViewController: LockIconViewController
+ @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
+ @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+ @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+ @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
@Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ @Mock
+ lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -118,43 +103,44 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(view.bottom).thenReturn(VIEW_BOTTOM)
whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container))
- .thenReturn(mock(ViewGroup::class.java))
+ .thenReturn(mock(ViewGroup::class.java))
whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java)))
- .thenReturn(keyguardBouncerComponent)
+ .thenReturn(keyguardBouncerComponent)
whenever(keyguardBouncerComponent.securityContainerController)
- .thenReturn(keyguardSecurityContainerController)
+ .thenReturn(keyguardSecurityContainerController)
whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
- .thenReturn(emptyFlow<TransitionStep>())
- underTest = NotificationShadeWindowViewController(
- lockscreenShadeTransitionController,
- FalsingCollectorFake(),
- sysuiStatusBarStateController,
- dockManager,
- notificationShadeDepthController,
- view,
- notificationPanelViewController,
- ShadeExpansionStateManager(),
- stackScrollLayoutController,
- statusBarKeyguardViewManager,
- statusBarWindowStateController,
- lockIconViewController,
- centralSurfaces,
- notificationShadeWindowController,
- keyguardUnlockAnimationController,
- notificationInsetsController,
- ambientState,
- pulsingGestureListener,
- keyguardBouncerViewModel,
- keyguardBouncerComponentFactory,
- alternateBouncerInteractor,
- keyguardTransitionInteractor,
- )
+ .thenReturn(emptyFlow<TransitionStep>())
+ underTest =
+ NotificationShadeWindowViewController(
+ lockscreenShadeTransitionController,
+ FalsingCollectorFake(),
+ sysuiStatusBarStateController,
+ dockManager,
+ notificationShadeDepthController,
+ view,
+ notificationPanelViewController,
+ ShadeExpansionStateManager(),
+ stackScrollLayoutController,
+ statusBarKeyguardViewManager,
+ statusBarWindowStateController,
+ lockIconViewController,
+ centralSurfaces,
+ notificationShadeWindowController,
+ keyguardUnlockAnimationController,
+ notificationInsetsController,
+ ambientState,
+ pulsingGestureListener,
+ keyguardBouncerViewModel,
+ keyguardBouncerComponentFactory,
+ alternateBouncerInteractor,
+ keyguardTransitionInteractor,
+ primaryBouncerToGoneTransitionViewModel,
+ )
underTest.setupExpandedStatusBar()
- interactionEventHandlerCaptor =
- ArgumentCaptor.forClass(InteractionEventHandler::class.java)
+ interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java)
verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
- interactionEventHandler = interactionEventHandlerCaptor.value
+ interactionEventHandler = interactionEventHandlerCaptor.value
}
// Note: So far, these tests only cover interactions with the status bar view controller. More
@@ -184,14 +170,11 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Test
fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
underTest.setStatusBarViewController(phoneStatusBarViewController)
- val downEvBelow = MotionEvent.obtain(
- 0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0
- )
+ val downEvBelow =
+ MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
- val nextEvent = MotionEvent.obtain(
- 0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0
- )
+ val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 5d719790386a..faa6221b675c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -46,6 +46,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationInsetsController;
@@ -101,6 +102,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private NotificationInsetsController mNotificationInsetsController;
@Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -150,7 +152,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mKeyguardBouncerViewModel,
mKeyguardBouncerComponentFactory,
mAlternateBouncerInteractor,
- mKeyguardTransitionInteractor
+ mKeyguardTransitionInteractor,
+ mPrimaryBouncerToGoneTransitionViewModel
);
mController.setupExpandedStatusBar();
mController.setDragDownHelper(mDragDownHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
new file mode 100644
index 000000000000..eac0e296c51f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.notification.collection.coordinator
+
+import android.app.Notification
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.SbnBuilder
+import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeFinalizeFilterListener
+import com.android.systemui.statusbar.notification.collection.render.NotifGroupController
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.mockito.withArgCaptor
+import com.android.systemui.util.time.SystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.InjectMocks
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class GroupWhenCoordinatorTest : SysuiTestCase() {
+
+ private lateinit var beforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
+ private lateinit var afterRenderGroupListener: OnAfterRenderGroupListener
+
+ @Mock private lateinit var pipeline: NotifPipeline
+
+ @Mock private lateinit var delayableExecutor: DelayableExecutor
+
+ @Mock private lateinit var groupController: NotifGroupController
+
+ @Mock private lateinit var systemClock: SystemClock
+
+ @InjectMocks private lateinit var coordinator: GroupWhenCoordinator
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+ whenever(systemClock.currentTimeMillis()).thenReturn(NOW)
+ coordinator.attach(pipeline)
+
+ beforeFinalizeFilterListener = withArgCaptor {
+ verify(pipeline).addOnBeforeFinalizeFilterListener(capture())
+ }
+ afterRenderGroupListener = withArgCaptor {
+ verify(pipeline).addOnAfterRenderGroupListener(capture())
+ }
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreBeforeNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW - 10L)
+ val childEntry2 = buildNotificationEntry(2, NOW - 100L)
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW - 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestTimeByNow_whenAllNotificationsAreAfterNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 10L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 100L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setClosestFutureTimeByNow_whenThereAreBothBeforeAndAfterNow() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 100L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 10L)
+ val childEntry3 = buildNotificationEntry(3, NOW - 100L)
+ val childEntry4 = buildNotificationEntry(4, NOW - 9L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3, childEntry4))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 10L))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_filterInvalidNotificationTimes() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 100L)
+ val childEntry2 = buildNotificationEntry(2, -20000L)
+ val childEntry3 = buildNotificationEntry(4, 0)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController).setNotificationGroupWhen(eq(NOW + 100))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_setSummaryTimeWhenAllNotificationTimesAreInvalid() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, 0)
+ val childEntry2 = buildNotificationEntry(2, -1)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(groupController, never()).setNotificationGroupWhen(NOW)
+ }
+
+ @Test
+ fun setNotificationGroupWhen_schedulePipelineInvalidationWhenAnyNotificationIsInTheFuture() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 1000L)
+ val childEntry2 = buildNotificationEntry(2, NOW + 2000L)
+ val childEntry3 = buildNotificationEntry(3, NOW - 100L)
+
+ val groupEntry =
+ GroupEntryBuilder()
+ .setSummary(summaryEntry)
+ .setChildren(listOf(childEntry1, childEntry2, childEntry3))
+ .build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ // THEN
+ verify(delayableExecutor).executeDelayed(any(), eq(1000))
+ }
+
+ @Test
+ fun setNotificationGroupWhen_cancelPrevPipelineInvalidation() {
+ // GIVEN
+ val summaryEntry = buildNotificationEntry(0, NOW)
+ val childEntry1 = buildNotificationEntry(1, NOW + 1L)
+ val prevInvalidation = mock<Runnable>()
+ whenever(delayableExecutor.executeDelayed(any(), any())).thenReturn(prevInvalidation)
+
+ val groupEntry =
+ GroupEntryBuilder().setSummary(summaryEntry).setChildren(listOf(childEntry1)).build()
+
+ // WHEN
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+ afterRenderGroupListener.onAfterRenderGroup(groupEntry, groupController)
+
+ beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+ // THEN
+ verify(prevInvalidation).run()
+ }
+
+ private fun buildNotificationEntry(id: Int, timeMillis: Long): NotificationEntry {
+ val notification = Notification.Builder(mContext).setWhen(timeMillis).build()
+ val sbn = SbnBuilder().setNotification(notification).build()
+ return NotificationEntryBuilder().setId(id).setSbn(sbn).build()
+ }
+
+ private companion object {
+ private const val NOW = 1000L
+ }
+}
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 dd7143ae7e16..cbf841b5a1f7 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.stack;
import static android.view.View.GONE;
+import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
@@ -46,6 +47,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -54,6 +56,8 @@ import android.util.MathUtils;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
@@ -91,6 +95,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
@@ -843,6 +849,19 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
verify(mEmptyShadeView).setFooterText(not(0));
}
+ @Test
+ public void testWindowInsetAnimationProgress_updatesBottomInset() {
+ int bottomImeInset = 100;
+ mStackScrollerInternal.setAnimatedInsetsEnabled(true);
+ WindowInsets windowInsets = new WindowInsets.Builder()
+ .setInsets(ime(), Insets.of(0, 0, 0, bottomImeInset)).build();
+ ArrayList<WindowInsetsAnimation> windowInsetsAnimations = new ArrayList<>();
+ mStackScrollerInternal
+ .dispatchWindowInsetsAnimationProgress(windowInsets, windowInsetsAnimations);
+
+ assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset);
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index eb5edbc21d89..f5b7ca804fbc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -180,6 +180,7 @@ public class DozeParametersTest extends SysuiTestCase {
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1");
+ verify(mScreenOffAnimationController).onAlwaysOnChanged(false);
assertThat(mDozeParameters.getAlwaysOn()).isFalse();
}
@@ -196,13 +197,16 @@ public class DozeParametersTest extends SysuiTestCase {
mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
verify(callback, times(2)).onAlwaysOnChange();
+ verify(mScreenOffAnimationController, times(2)).onAlwaysOnChanged(false);
assertThat(mDozeParameters.getAlwaysOn()).isFalse();
+ reset(mScreenOffAnimationController);
reset(callback);
when(mBatteryController.isAodPowerSave()).thenReturn(false);
mBatteryStateChangeCallback.getValue().onPowerSaveChanged(true);
verify(callback).onAlwaysOnChange();
+ verify(mScreenOffAnimationController).onAlwaysOnChanged(true);
assertThat(mDozeParameters.getAlwaysOn()).isTrue();
}
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 dc5a0472f49e..e1fba816382c 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,6 +24,8 @@ 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;
@@ -58,8 +60,14 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
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.KeyguardState;
+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.statusbar.policy.FakeConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -85,8 +93,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import kotlinx.coroutines.CoroutineDispatcher;
+
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class ScrimControllerTest extends SysuiTestCase {
@@ -115,6 +125,11 @@ public class ScrimControllerTest extends SysuiTestCase {
@Mock private DockManager mDockManager;
@Mock private ScreenOffAnimationController mScreenOffAnimationController;
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
+ @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;
@@ -225,13 +240,22 @@ public class ScrimControllerTest extends SysuiTestCase {
when(mDelayedWakeLockBuilder.build()).thenReturn(mWakeLock);
when(mDockManager.isDocked()).thenReturn(false);
+ when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
+ .thenReturn(emptyFlow());
+ when(mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha())
+ .thenReturn(emptyFlow());
+
mScrimController = new ScrimController(mLightBarController,
mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mPrimaryBouncerToGoneTransitionViewModel,
+ mKeyguardTransitionInteractor,
+ mSysuiStatusBarStateController,
+ mMainDispatcher);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -861,7 +885,11 @@ public class ScrimControllerTest extends SysuiTestCase {
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
mKeyguardUnlockAnimationController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mPrimaryBouncerToGoneTransitionViewModel,
+ mKeyguardTransitionInteractor,
+ mSysuiStatusBarStateController,
+ mMainDispatcher);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
mScrimController.setAnimatorListener(mAnimatorListener);
@@ -1629,6 +1657,28 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimAlpha(mScrimBehind, 0);
}
+ @Test
+ public void ignoreTransitionRequestWhileKeyguardTransitionRunning() {
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.mPrimaryBouncerToGoneTransition.accept(
+ new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
+ TransitionState.RUNNING, "ScrimControllerTest"));
+
+ // This request should not happen
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ assertThat(mScrimController.getState()).isEqualTo(ScrimState.UNLOCKED);
+ }
+
+ @Test
+ public void primaryBouncerToGoneOnFinishCallsKeyguardFadedAway() {
+ when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
+ mScrimController.mPrimaryBouncerToGoneTransition.accept(
+ new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f,
+ TransitionState.FINISHED, "ScrimControllerTest"));
+
+ verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway();
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
index 1779de729e5b..7594c90daa8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt
@@ -8,13 +8,14 @@ import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.TestUnfoldTransitionProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -26,6 +27,9 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
@Mock
private lateinit var display: Display
+ @Mock
+ private lateinit var currentActivityTypeProvider: CurrentActivityTypeProvider
+
private val view: View = View(context)
private val progressProvider = TestUnfoldTransitionProvider()
private val scopedProvider = ScopedUnfoldTransitionProgressProvider(progressProvider)
@@ -36,9 +40,9 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(windowManager.defaultDisplay).thenReturn(display)
- `when`(display.rotation).thenReturn(Surface.ROTATION_0)
- `when`(display.getSize(any())).thenAnswer {
+ whenever(windowManager.defaultDisplay).thenReturn(display)
+ whenever(display.rotation).thenReturn(Surface.ROTATION_0)
+ whenever(display.getSize(any())).thenAnswer {
val point = it.arguments[0] as Point
point.x = 100
point.y = 100
@@ -47,7 +51,12 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
scopedProvider.setReadyToHandleTransition(true)
- controller = StatusBarMoveFromCenterAnimationController(scopedProvider, windowManager)
+ controller =
+ StatusBarMoveFromCenterAnimationController(
+ scopedProvider,
+ currentActivityTypeProvider,
+ windowManager
+ )
}
@Test
@@ -99,6 +108,31 @@ class StatusBarMoveFromCenterAnimationControllerTest : SysuiTestCase() {
}
@Test
+ fun alpha_onLauncher_alphaDoesNotChange() {
+ whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(true)
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0.0f)
+ assertThat(view.alpha).isEqualTo(1.0f)
+
+ progressProvider.onTransitionProgress(1.0f)
+
+ assertThat(view.alpha).isEqualTo(1.0f)
+ }
+
+ @Test
+ fun alpha_NotOnLauncher_alphaChanges() {
+ whenever(currentActivityTypeProvider.isHomeActivity).thenReturn(false)
+ controller.onViewsReady(arrayOf(view))
+ progressProvider.onTransitionStarted()
+ assertThat(view.alpha).isEqualTo(1.0f)
+
+ progressProvider.onTransitionProgress(0.5f)
+
+ assertThat(view.alpha).isNotEqualTo(1.0f)
+ }
+
+ @Test
fun transitionFinished_viewReAttached_noChangesToTranslation() {
controller.onViewsReady(arrayOf(view))
progressProvider.onTransitionProgress(0.5f)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
index 86529dce948a..35dea60b1a1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/shared/MobileInputLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.mobile.shared
+package com.android.systemui.statusbar.pipeline.mobile.data
import android.net.Network
import android.net.NetworkCapabilities
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
index 0145103d55e1..dfef62e95eda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt
@@ -24,8 +24,8 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 17502f28a479..07c8cee9a3d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -27,13 +27,13 @@ import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
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 b2577e349da7..bd5a4d7f7385 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
@@ -53,6 +53,7 @@ import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
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.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
@@ -65,7 +66,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrier
import com.android.systemui.statusbar.pipeline.mobile.data.model.toNetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 09b7a66c925d..68b1cda62f4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,12 +37,12 @@ import com.android.settingslib.mobile.MobileMappings
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.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
-import com.android.systemui.statusbar.pipeline.mobile.shared.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
new file mode 100644
index 000000000000..4aa48d6f25f1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLoggerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.ui
+
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.KeyguardMobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class MobileViewLoggerTest : SysuiTestCase() {
+ private val buffer = LogBufferFactory(DumpManager(), mock()).create("buffer", 10)
+ private val stringWriter = StringWriter()
+ private val printWriter = PrintWriter(stringWriter)
+
+ private val underTest = MobileViewLogger(buffer, mock())
+
+ @Mock private lateinit var flags: StatusBarPipelineFlags
+ @Mock private lateinit var commonViewModel: MobileIconViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun collectionStarted_dumpHasInfo() {
+ val view = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true")
+ }
+
+ @Test
+ fun collectionStarted_multipleViews_dumpHasInfo() {
+ val view = TextView(context)
+ val view2 = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+ val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+ underTest.logCollectionStarted(view2, viewModel2)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=true")
+ assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true")
+ }
+
+ @Test
+ fun collectionStopped_dumpHasInfo() {
+ val view = TextView(context)
+ val view2 = TextView(context)
+ val viewModel = QsMobileIconViewModel(commonViewModel, flags)
+ val viewModel2 = KeyguardMobileIconViewModel(commonViewModel, flags)
+
+ underTest.logCollectionStarted(view, viewModel)
+ underTest.logCollectionStarted(view2, viewModel2)
+ underTest.logCollectionStopped(view, viewModel)
+
+ val dumpString = getDumpString()
+ assertThat(dumpString).contains("${view.getIdForLogging()}, isCollecting=false")
+ assertThat(dumpString).contains("${view2.getIdForLogging()}, isCollecting=true")
+ }
+
+ private fun getDumpString(): String {
+ underTest.dump(printWriter, args = arrayOf())
+ return stringWriter.toString()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index e68a3970ae93..7420db2e895e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.QsMobileIconViewModel
@@ -60,6 +61,7 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
+ @Mock private lateinit var viewLogger: MobileViewLogger
@Mock private lateinit var constants: ConnectivityConstants
private lateinit var interactor: FakeMobileIconInteractor
private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -94,7 +96,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
@Test
fun setVisibleState_icon_iconShownDotHidden() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
@@ -109,8 +117,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
@Test
fun setVisibleState_dot_iconHiddenDotShown() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
ViewUtils.attachView(view)
@@ -124,8 +137,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
@Test
fun setVisibleState_hidden_iconAndDotHidden() {
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
ViewUtils.attachView(view)
@@ -142,8 +160,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
whenever(constants.hasDataCapabilities).thenReturn(false)
createViewModel()
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -157,8 +180,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
whenever(constants.hasDataCapabilities).thenReturn(true)
createViewModel()
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -171,8 +199,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
fun isIconVisible_notAirplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -185,8 +218,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
fun isIconVisible_airplaneMode_outputsTrue() {
airplaneModeRepository.setIsAirplaneMode(true)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
-
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -198,7 +236,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
@Test
fun onDarkChanged_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
@@ -214,7 +258,13 @@ class ModernStatusBarMobileViewTest : SysuiTestCase() {
@Test
fun setStaticDrawableColor_iconHasNewColor() {
whenever(statusBarPipelineFlags.useDebugColoring()).thenReturn(false)
- val view = ModernStatusBarMobileView.constructAndBind(context, SLOT_NAME, viewModel)
+ val view =
+ ModernStatusBarMobileView.constructAndBind(
+ context,
+ viewLogger,
+ SLOT_NAME,
+ viewModel,
+ )
ViewUtils.attachView(view)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
index f9830309252d..a6d915243f60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
@@ -84,7 +85,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
testScope.backgroundScope,
)
- homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags)
+ homeIcon = HomeMobileIconViewModel(commonImpl, statusBarPipelineFlags, mock())
qsIcon = QsMobileIconViewModel(commonImpl, statusBarPipelineFlags)
keyguardIcon = KeyguardMobileIconViewModel(commonImpl, statusBarPipelineFlags)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index 4628f8410245..ddb7f4d88d30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -24,6 +24,8 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirp
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
+import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
@@ -51,6 +53,8 @@ class MobileIconsViewModelTest : SysuiTestCase() {
private lateinit var airplaneModeInteractor: AirplaneModeInteractor
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var constants: ConnectivityConstants
+ @Mock private lateinit var logger: MobileViewLogger
+ @Mock private lateinit var verboseLogger: VerboseMobileViewLogger
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -73,6 +77,8 @@ class MobileIconsViewModelTest : SysuiTestCase() {
underTest =
MobileIconsViewModel(
subscriptionIdsFlow,
+ logger,
+ verboseLogger,
interactor,
airplaneModeInteractor,
constants,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
index e4c8fd0cd8a1..b4039d906810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarViewTest.kt
@@ -164,6 +164,10 @@ class ModernStatusBarViewTest : SysuiTestCase() {
override fun getShouldIconBeVisible(): Boolean {
return shouldIconBeVisibleInternal
}
+
+ override fun isCollecting(): Boolean {
+ return true
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 48b17322da4d..481d453fa0b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -38,6 +38,7 @@ import com.android.internal.R;
import com.android.internal.view.RotationPolicy;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.wrapper.RotationPolicyWrapper;
@@ -55,10 +56,12 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase
private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"};
+ @Mock private DeviceStateManager mDeviceStateManager;
+ @Mock private DeviceStateRotationLockSettingControllerLogger mLogger;
+ @Mock private DumpManager mDumpManager;
+
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
- @Mock
- private DeviceStateManager mDeviceStateManager;
private final RotationPolicyWrapper mFakeRotationPolicy = new FakeRotationPolicy();
private DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
private DeviceStateManager.DeviceStateCallback mDeviceStateCallback;
@@ -78,7 +81,13 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase
mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext);
mDeviceStateRotationLockSettingController =
new DeviceStateRotationLockSettingController(
- mFakeRotationPolicy, mDeviceStateManager, mFakeExecutor, mSettingsManager);
+ mFakeRotationPolicy,
+ mDeviceStateManager,
+ mFakeExecutor,
+ mSettingsManager,
+ mLogger,
+ mDumpManager
+ );
mDeviceStateRotationLockSettingController.setListening(true);
verify(mDeviceStateManager)
@@ -173,15 +182,11 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase
}
@Test
- public void whenDeviceStateSwitchedToIgnoredState_usePreviousSetting() {
- initializeSettingsWith(
- 0, DEVICE_STATE_ROTATION_LOCK_IGNORED, 1, DEVICE_STATE_ROTATION_LOCK_UNLOCKED);
- mFakeRotationPolicy.setRotationLock(true);
-
- mDeviceStateCallback.onStateChanged(1);
- assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
-
+ public void whenDeviceStateSwitchedToIgnoredState_useFallbackSetting() {
mDeviceStateCallback.onStateChanged(0);
+ assertThat(mFakeRotationPolicy.isRotationLocked()).isTrue();
+
+ mDeviceStateCallback.onStateChanged(2);
assertThat(mFakeRotationPolicy.isRotationLocked()).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
index f8bf4b91e11a..4525ad27b749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -65,8 +65,6 @@ class StylusManagerTest : SysuiTestCase() {
@Mock lateinit var uiEventLogger: UiEventLogger
@Mock lateinit var stylusCallback: StylusManager.StylusCallback
@Mock lateinit var otherStylusCallback: StylusManager.StylusCallback
- @Mock lateinit var stylusBatteryCallback: StylusManager.StylusBatteryCallback
- @Mock lateinit var otherStylusBatteryCallback: StylusManager.StylusBatteryCallback
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var stylusManager: StylusManager
@@ -123,7 +121,6 @@ class StylusManagerTest : SysuiTestCase() {
stylusManager.startListener()
stylusManager.registerCallback(stylusCallback)
- stylusManager.registerBatteryCallback(stylusBatteryCallback)
clearInvocations(inputManager)
}
@@ -434,23 +431,6 @@ class StylusManagerTest : SysuiTestCase() {
}
@Test
- fun onMetadataChanged_multipleRegisteredBatteryCallbacks_executesAll() {
- stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
- stylusManager.registerBatteryCallback(otherStylusBatteryCallback)
-
- stylusManager.onMetadataChanged(
- bluetoothDevice,
- BluetoothDevice.METADATA_MAIN_CHARGING,
- "true".toByteArray()
- )
-
- verify(stylusBatteryCallback, times(1))
- .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
- verify(otherStylusBatteryCallback, times(1))
- .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
- }
-
- @Test
fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() {
stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
@@ -460,7 +440,7 @@ class StylusManagerTest : SysuiTestCase() {
"true".toByteArray()
)
- verify(stylusBatteryCallback, times(1))
+ verify(stylusCallback, times(1))
.onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
}
@@ -474,7 +454,7 @@ class StylusManagerTest : SysuiTestCase() {
"false".toByteArray()
)
- verify(stylusBatteryCallback, times(1))
+ verify(stylusCallback, times(1))
.onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, false)
}
@@ -486,7 +466,7 @@ class StylusManagerTest : SysuiTestCase() {
"true".toByteArray()
)
- verifyNoMoreInteractions(stylusBatteryCallback)
+ verifyNoMoreInteractions(stylusCallback)
}
@Test
@@ -499,8 +479,7 @@ class StylusManagerTest : SysuiTestCase() {
"true".toByteArray()
)
- verify(stylusBatteryCallback, never())
- .onStylusBluetoothChargingStateChanged(any(), any(), any())
+ verify(stylusCallback, never()).onStylusBluetoothChargingStateChanged(any(), any(), any())
}
@Test
@@ -614,7 +593,7 @@ class StylusManagerTest : SysuiTestCase() {
fun onBatteryStateChanged_executesBatteryCallbacks() {
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
- verify(stylusBatteryCallback, times(1))
+ verify(stylusCallback, times(1))
.onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
index 82b80f53d6b0..3db0ecc4e8df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
@@ -96,7 +96,6 @@ class StylusUsiPowerStartableTest : SysuiTestCase() {
startable.start()
verify(stylusManager, times(1)).registerCallback(startable)
- verify(stylusManager, times(1)).registerBatteryCallback(startable)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
index 85cfef727954..fd368eb07b5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt
@@ -16,22 +16,24 @@
package com.android.systemui.unfold.updates
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.os.Looper
import android.testing.AndroidTestingRunner
-import android.view.IRotationWatcher
-import android.view.IWindowManager
+import android.view.Display
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.RotationChangeProvider.RotationListener
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.os.FakeHandler
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@@ -42,19 +44,23 @@ class RotationChangeProviderTest : SysuiTestCase() {
private lateinit var rotationChangeProvider: RotationChangeProvider
- @Mock lateinit var windowManagerInterface: IWindowManager
+ @Mock lateinit var displayManager: DisplayManager
@Mock lateinit var listener: RotationListener
- @Captor lateinit var rotationWatcher: ArgumentCaptor<IRotationWatcher>
- private val fakeExecutor = FakeExecutor(FakeSystemClock())
+ @Mock lateinit var display: Display
+ @Captor lateinit var displayListener: ArgumentCaptor<DisplayManager.DisplayListener>
+ private val fakeHandler = FakeHandler(Looper.getMainLooper())
+
+ private lateinit var spyContext: Context
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- rotationChangeProvider =
- RotationChangeProvider(windowManagerInterface, context, fakeExecutor)
+ spyContext = spy(context)
+ whenever(spyContext.display).thenReturn(display)
+ rotationChangeProvider = RotationChangeProvider(displayManager, spyContext, fakeHandler)
rotationChangeProvider.addCallback(listener)
- fakeExecutor.runAllReady()
- verify(windowManagerInterface).watchRotation(rotationWatcher.capture(), anyInt())
+ fakeHandler.dispatchQueuedMessages()
+ verify(displayManager).registerDisplayListener(displayListener.capture(), any())
}
@Test
@@ -70,15 +76,16 @@ class RotationChangeProviderTest : SysuiTestCase() {
verify(listener).onRotationChanged(42)
rotationChangeProvider.removeCallback(listener)
- fakeExecutor.runAllReady()
+ fakeHandler.dispatchQueuedMessages()
sendRotationUpdate(43)
- verify(windowManagerInterface).removeRotationWatcher(any())
+ verify(displayManager).unregisterDisplayListener(any())
verifyNoMoreInteractions(listener)
}
private fun sendRotationUpdate(newRotation: Int) {
- rotationWatcher.value.onRotationChanged(newRotation)
- fakeExecutor.runAllReady()
+ whenever(display.rotation).thenReturn(newRotation)
+ displayListener.allValues.forEach { it.onDisplayChanged(display.displayId) }
+ fakeHandler.dispatchQueuedMessages()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 31cce4f3168b..468c5a73645b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -88,7 +88,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
@Mock
private Bitmap mWallpaperBitmap;
FakeSystemClock mFakeSystemClock = new FakeSystemClock();
- FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
+ FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
@Before
public void setUp() throws Exception {
@@ -125,7 +125,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
@Test
public void testBitmapWallpaper_normal() {
- // Will use a image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
+ // Will use an image wallpaper with dimensions DISPLAY_WIDTH x DISPLAY_WIDTH.
// Then we expect the surface size will be also DISPLAY_WIDTH x DISPLAY_WIDTH.
int bitmapSide = DISPLAY_WIDTH;
testSurfaceHelper(
@@ -137,7 +137,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
@Test
public void testBitmapWallpaper_low_resolution() {
- // Will use a image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
+ // Will use an image wallpaper with dimensions BMP_WIDTH x BMP_HEIGHT.
// Then we expect the surface size will be also BMP_WIDTH x BMP_HEIGHT.
testSurfaceHelper(LOW_BMP_WIDTH /* bitmapWidth */,
LOW_BMP_HEIGHT /* bitmapHeight */,
@@ -161,13 +161,13 @@ public class ImageWallpaperTest extends SysuiTestCase {
ImageWallpaper.CanvasEngine spyEngine = getSpyEngine();
spyEngine.onCreate(mSurfaceHolder);
spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
- assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
+ assertThat(mFakeExecutor.numPending()).isAtLeast(1);
int n = 0;
- while (mFakeBackgroundExecutor.numPending() >= 1) {
+ while (mFakeExecutor.numPending() >= 1) {
n++;
assertThat(n).isAtMost(10);
- mFakeBackgroundExecutor.runNextReady();
+ mFakeExecutor.runNextReady();
mFakeSystemClock.advanceTime(1000);
}
@@ -176,7 +176,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper(mFakeBackgroundExecutor, mUserTracker) {
+ return new ImageWallpaper(mFakeExecutor, mUserTracker) {
@Override
public Engine onCreateEngine() {
return new CanvasEngine() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
new file mode 100644
index 000000000000..4e435462be50
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/data/repository/FakeKeyboardRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.keyboard.data.repository
+
+import com.android.systemui.keyboard.shared.model.BacklightModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeKeyboardRepository : KeyboardRepository {
+
+ private val _keyboardConnected = MutableStateFlow(false)
+ override val keyboardConnected: Flow<Boolean> = _keyboardConnected
+
+ private val _backlightState: MutableStateFlow<BacklightModel?> = MutableStateFlow(null)
+ // filtering to make sure backlight doesn't have default initial value
+ override val backlight: Flow<BacklightModel> = _backlightState.filterNotNull()
+
+ fun setBacklight(state: BacklightModel) {
+ _backlightState.value = state
+ }
+
+ fun setKeyboardConnected(connected: Boolean) {
+ _keyboardConnected.value = connected
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 1a371c73550c..194ed02712b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -47,6 +47,9 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _isKeyguardShowing = MutableStateFlow(false)
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
+ private val _isKeyguardUnlocked = MutableStateFlow(false)
+ override val isKeyguardUnlocked: Flow<Boolean> = _isKeyguardUnlocked
+
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index eac1bd145033..16442bb525b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -37,7 +37,7 @@ class FakeKeyguardTransitionRepository : KeyguardTransitionRepository {
_transitions.emit(step)
}
- override fun startTransition(info: TransitionInfo): UUID? {
+ override fun startTransition(info: TransitionInfo, resetIfCanceled: Boolean): UUID? {
return null
}
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
index 180b611aa13b..2e0a9462ffbe 100644
--- a/packages/SystemUI/unfold/Android.bp
+++ b/packages/SystemUI/unfold/Android.bp
@@ -35,6 +35,7 @@ android_library {
],
kotlincflags: ["-Xjvm-default=enable"],
java_version: "1.8",
+ sdk_version: "current",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index 068347cfe9d8..c3a6cf035d09 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -19,14 +19,15 @@ package com.android.systemui.unfold
import android.content.ContentResolver
import android.content.Context
import android.hardware.SensorManager
+import android.hardware.display.DisplayManager
import android.os.Handler
-import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.RotationChangeProvider
+import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
@@ -61,12 +62,13 @@ interface UnfoldSharedComponent {
@BindsInstance @UnfoldMain executor: Executor,
@BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
- @BindsInstance windowManager: IWindowManager,
+ @BindsInstance displayManager: DisplayManager,
@BindsInstance contentResolver: ContentResolver = context.contentResolver
): UnfoldSharedComponent
}
val unfoldTransitionProvider: Optional<UnfoldTransitionProgressProvider>
+ val hingeAngleProvider: HingeAngleProvider
val rotationChangeProvider: RotationChangeProvider
}
@@ -84,8 +86,9 @@ interface RemoteUnfoldSharedComponent {
@BindsInstance context: Context,
@BindsInstance config: UnfoldTransitionConfig,
@BindsInstance @UnfoldMain executor: Executor,
+ @BindsInstance @UnfoldMain handler: Handler,
@BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor,
- @BindsInstance windowManager: IWindowManager,
+ @BindsInstance displayManager: DisplayManager,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
): RemoteUnfoldSharedComponent
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index 8eb79df55496..18399194434a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -19,8 +19,8 @@ package com.android.systemui.unfold
import android.content.Context
import android.hardware.SensorManager
+import android.hardware.display.DisplayManager
import android.os.Handler
-import android.view.IWindowManager
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
@@ -47,7 +47,7 @@ fun createUnfoldSharedComponent(
mainExecutor: Executor,
singleThreadBgExecutor: Executor,
tracingTagPrefix: String,
- windowManager: IWindowManager,
+ displayManager: DisplayManager,
): UnfoldSharedComponent =
DaggerUnfoldSharedComponent.factory()
.create(
@@ -61,7 +61,7 @@ fun createUnfoldSharedComponent(
mainExecutor,
singleThreadBgExecutor,
tracingTagPrefix,
- windowManager,
+ displayManager,
)
/**
@@ -73,16 +73,18 @@ fun createRemoteUnfoldSharedComponent(
context: Context,
config: UnfoldTransitionConfig,
mainExecutor: Executor,
+ mainHandler: Handler,
singleThreadBgExecutor: Executor,
tracingTagPrefix: String,
- windowManager: IWindowManager,
+ displayManager: DisplayManager,
): RemoteUnfoldSharedComponent =
DaggerRemoteUnfoldSharedComponent.factory()
.create(
context,
config,
mainExecutor,
+ mainHandler,
singleThreadBgExecutor,
- windowManager,
+ displayManager,
tracingTagPrefix,
)
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index d19b414cb963..28e493651137 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -16,7 +16,6 @@
package com.android.systemui.unfold.progress
import android.os.Trace
-import android.os.Trace.TRACE_TAG_APP
import android.util.Log
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -110,7 +109,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor(
if (DEBUG) {
Log.d(TAG, "onFoldUpdate = ${update.name()}")
- Trace.traceCounter(Trace.TRACE_TAG_APP, "fold_update", update)
+ Trace.setCounter("fold_update", update.toLong())
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 82fd2258120a..d653fc7beff2 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -119,7 +119,7 @@ constructor(
"lastHingeAngle: $lastHingeAngle, " +
"lastHingeAngleBeforeTransition: $lastHingeAngleBeforeTransition"
)
- Trace.traceCounter(Trace.TRACE_TAG_APP, "hinge_angle", angle.toInt())
+ Trace.setCounter( "hinge_angle", angle.toLong())
}
val currentDirection =
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index 0cf8224d3a3f..ce8f1a178d05 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -17,36 +17,32 @@
package com.android.systemui.unfold.updates
import android.content.Context
+import android.hardware.display.DisplayManager
+import android.os.Handler
import android.os.RemoteException
-import android.view.IRotationWatcher
-import android.view.IWindowManager
-import android.view.Surface.Rotation
import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.util.CallbackController
-import java.util.concurrent.Executor
import javax.inject.Inject
/**
- * Allows to subscribe to rotation changes.
- *
- * This is needed as rotation updates from [IWindowManager] are received in a binder thread, while
- * most of the times we want them in the main one. Updates are provided for the display associated
+ * Allows to subscribe to rotation changes. Updates are provided for the display associated
* to [context].
*/
class RotationChangeProvider
@Inject
constructor(
- private val windowManagerInterface: IWindowManager,
+ private val displayManager: DisplayManager,
private val context: Context,
- @UnfoldMain private val mainExecutor: Executor,
+ @UnfoldMain private val mainHandler: Handler,
) : CallbackController<RotationChangeProvider.RotationListener> {
private val listeners = mutableListOf<RotationListener>()
- private val rotationWatcher = RotationWatcher()
+ private val displayListener = RotationDisplayListener()
+ private var lastRotation: Int? = null
override fun addCallback(listener: RotationListener) {
- mainExecutor.execute {
+ mainHandler.post {
if (listeners.isEmpty()) {
subscribeToRotation()
}
@@ -55,17 +51,18 @@ constructor(
}
override fun removeCallback(listener: RotationListener) {
- mainExecutor.execute {
+ mainHandler.post {
listeners -= listener
if (listeners.isEmpty()) {
unsubscribeToRotation()
+ lastRotation = null
}
}
}
private fun subscribeToRotation() {
try {
- windowManagerInterface.watchRotation(rotationWatcher, context.displayId)
+ displayManager.registerDisplayListener(displayListener, mainHandler)
} catch (e: RemoteException) {
throw e.rethrowFromSystemServer()
}
@@ -73,7 +70,7 @@ constructor(
private fun unsubscribeToRotation() {
try {
- windowManagerInterface.removeRotationWatcher(rotationWatcher)
+ displayManager.unregisterDisplayListener(displayListener)
} catch (e: RemoteException) {
throw e.rethrowFromSystemServer()
}
@@ -82,12 +79,25 @@ constructor(
/** Gets notified of rotation changes. */
fun interface RotationListener {
/** Called once rotation changes. */
- fun onRotationChanged(@Rotation newRotation: Int)
+ fun onRotationChanged(newRotation: Int)
}
- private inner class RotationWatcher : IRotationWatcher.Stub() {
- override fun onRotationChanged(rotation: Int) {
- mainExecutor.execute { listeners.forEach { it.onRotationChanged(rotation) } }
+ private inner class RotationDisplayListener : DisplayManager.DisplayListener {
+
+ override fun onDisplayChanged(displayId: Int) {
+ val display = context.display ?: return
+
+ if (displayId == display.displayId) {
+ val currentRotation = display.rotation
+ if (lastRotation == null || lastRotation != currentRotation) {
+ listeners.forEach { it.onRotationChanged(currentRotation) }
+ lastRotation = currentRotation
+ }
+ }
}
+
+ override fun onDisplayAdded(displayId: Int) {}
+
+ override fun onDisplayRemoved(displayId: Int) {}
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index 06ca153b694b..ce5c5f91914b 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -79,10 +79,9 @@ constructor(
companion object {
fun ContentResolver.areAnimationsEnabled(): Boolean {
val animationScale =
- Settings.Global.getStringForUser(
+ Settings.Global.getString(
this,
Settings.Global.ANIMATOR_DURATION_SCALE,
- this.userId
)
?.toFloatOrNull()
?: 1f
diff --git a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
index a94ab34bc8b3..fa30a6f419f9 100644
--- a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
+++ b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
@@ -255,7 +255,7 @@ class FlashNotificationsController {
broadcastFilter.addAction(ACTION_FLASH_NOTIFICATION_STOP_PREVIEW);
mFlashBroadcastReceiver = new FlashBroadcastReceiver();
mContext.registerReceiver(
- mFlashBroadcastReceiver, broadcastFilter, Context.RECEIVER_EXPORTED);
+ mFlashBroadcastReceiver, broadcastFilter, Context.RECEIVER_NOT_EXPORTED);
final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a35cae9dffda..542cc2f0a0a6 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -59,6 +59,8 @@ import android.companion.DeviceNotAssociatedException;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
+import android.companion.IOnMessageReceivedListener;
+import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
import android.content.ComponentName;
import android.content.Context;
@@ -232,7 +234,7 @@ public class CompanionDeviceManagerService extends SystemService {
/* cdmService */this, mAssociationStore);
mCompanionAppController = new CompanionApplicationController(
context, mAssociationStore, mDevicePresenceMonitor);
- mTransportManager = new CompanionTransportManager(context);
+ mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
@@ -601,6 +603,37 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
+ @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
+ public void addOnTransportsChangedListener(IOnTransportsChangedListener listener) {
+ mTransportManager.addListener(listener);
+ }
+
+ @Override
+ @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
+ public void removeOnTransportsChangedListener(IOnTransportsChangedListener listener) {
+ mTransportManager.removeListener(listener);
+ }
+
+ @Override
+ @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
+ public void sendMessage(int messageType, byte[] data, int[] associationIds) {
+ mTransportManager.sendMessage(messageType, data, associationIds);
+ }
+
+ @Override
+ @GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
+ public void addOnMessageReceivedListener(int messageType,
+ IOnMessageReceivedListener listener) {
+ mTransportManager.addListener(messageType, listener);
+ }
+
+ @Override
+ public void removeOnMessageReceivedListener(int messageType,
+ IOnMessageReceivedListener listener) {
+ mTransportManager.removeListener(messageType, listener);
+ }
+
+ @Override
public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
+ ", macAddress=" + deviceMacAddress);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 3fffdbecd0de..f3a949d29ff8 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -31,6 +31,7 @@ import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.DeviceNotAssociatedException;
+import android.companion.IOnMessageReceivedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
import android.companion.datatransfer.SystemDataTransferRequest;
@@ -40,6 +41,7 @@ import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
@@ -92,8 +94,18 @@ public class SystemDataTransferProcessor {
mAssociationStore = associationStore;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mTransportManager = transportManager;
- mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE,
- this::onReceivePermissionRestore);
+ IOnMessageReceivedListener messageListener = new IOnMessageReceivedListener() {
+ @Override
+ public void onMessageReceived(int associationId, byte[] data) throws RemoteException {
+ onReceivePermissionRestore(data);
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE, messageListener);
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mExecutor = Executors.newSingleThreadExecutor();
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 8dab231d1819..539020519f84 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -22,25 +22,32 @@ import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_P
import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
+import android.companion.AssociationInfo;
+import android.companion.IOnMessageReceivedListener;
+import android.companion.IOnTransportsChangedListener;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.os.Build;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
-import com.android.server.companion.transport.Transport.Listener;
+import com.android.server.companion.AssociationStore;
import java.io.IOException;
import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -63,31 +70,100 @@ public class CompanionTransportManager {
}
private final Context mContext;
+ private final AssociationStore mAssociationStore;
+ /** Association id -> Transport */
@GuardedBy("mTransports")
private final SparseArray<Transport> mTransports = new SparseArray<>();
-
@NonNull
- private final Map<Integer, Listener> mListeners = new HashMap<>();
+ private final RemoteCallbackList<IOnTransportsChangedListener> mTransportsListeners =
+ new RemoteCallbackList<>();
+ /** Message type -> IOnMessageReceivedListener */
+ @NonNull
+ private final SparseArray<IOnMessageReceivedListener> mMessageListeners = new SparseArray<>();
+
+ @Nullable
private Transport mTempTransport;
- public CompanionTransportManager(Context context) {
+ public CompanionTransportManager(Context context, AssociationStore associationStore) {
mContext = context;
+ mAssociationStore = associationStore;
}
/**
- * Add a message listener when a message is received for the message type
+ * Add a listener to receive callbacks when a message is received for the message type
*/
@GuardedBy("mTransports")
- public void addListener(int message, @NonNull Listener listener) {
- mListeners.put(message, listener);
+ public void addListener(int message, @NonNull IOnMessageReceivedListener listener) {
+ mMessageListeners.put(message, listener);
for (int i = 0; i < mTransports.size(); i++) {
mTransports.valueAt(i).addListener(message, listener);
}
}
/**
+ * Add a listener to receive callbacks when any of the transports is changed
+ */
+ @GuardedBy("mTransports")
+ public void addListener(IOnTransportsChangedListener listener) {
+ Slog.i(TAG, "Registering OnTransportsChangedListener");
+ mTransportsListeners.register(listener);
+ List<AssociationInfo> associations = new ArrayList<>();
+ for (int i = 0; i < mTransports.size(); i++) {
+ AssociationInfo association = mAssociationStore.getAssociationById(
+ mTransports.keyAt(i));
+ if (association != null) {
+ associations.add(association);
+ }
+ }
+ mTransportsListeners.broadcast(listener1 -> {
+ // callback to the current listener with all the associations of the transports
+ // immediately
+ if (listener1 == listener) {
+ try {
+ listener.onTransportsChanged(associations);
+ } catch (RemoteException ignored) {
+ }
+ }
+ });
+ }
+
+ /**
+ * Remove the listener for receiving callbacks when any of the transports is changed
+ */
+ public void removeListener(IOnTransportsChangedListener listener) {
+ mTransportsListeners.unregister(listener);
+ }
+
+ /**
+ * Remove the listener to stop receiving calbacks when a message is received for the given type
+ */
+ public void removeListener(int messageType, IOnMessageReceivedListener listener) {
+ mMessageListeners.remove(messageType);
+ }
+
+ /**
+ * Send a message to remote devices through the transports
+ */
+ @GuardedBy("mTransports")
+ public void sendMessage(int message, byte[] data, int[] associationIds) {
+ Slog.i(TAG, "Sending message 0x" + Integer.toHexString(message)
+ + " data length " + data.length);
+ for (int i = 0; i < associationIds.length; i++) {
+ if (mTransports.contains(associationIds[i])) {
+ try {
+ mTransports.get(associationIds[i]).sendMessage(message, data);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to send message 0x" + Integer.toHexString(message)
+ + " data length " + data.length + " to association "
+ + associationIds[i]);
+ }
+ }
+ }
+ }
+
+ /**
* For the moment, we only offer transporting of system data to built-in
* companion apps; future work will improve the security model to support
* third-party companion apps.
@@ -119,6 +195,8 @@ public class CompanionTransportManager {
}
initializeTransport(associationId, fd);
+
+ notifyOnTransportsChanged();
}
}
@@ -130,16 +208,35 @@ public class CompanionTransportManager {
mTransports.delete(associationId);
transport.stop();
}
+
+ notifyOnTransportsChanged();
}
}
@GuardedBy("mTransports")
+ private void notifyOnTransportsChanged() {
+ List<AssociationInfo> associations = new ArrayList<>();
+ for (int i = 0; i < mTransports.size(); i++) {
+ AssociationInfo association = mAssociationStore.getAssociationById(
+ mTransports.keyAt(i));
+ if (association != null) {
+ associations.add(association);
+ }
+ }
+ mTransportsListeners.broadcast(listener -> {
+ try {
+ listener.onTransportsChanged(associations);
+ } catch (RemoteException ignored) {
+ }
+ });
+ }
+
+ @GuardedBy("mTransports")
private void initializeTransport(int associationId, ParcelFileDescriptor fd) {
+ Slog.i(TAG, "Initializing transport");
if (!isSecureTransportEnabled()) {
Transport transport = new RawTransport(associationId, fd, mContext);
- for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
- transport.addListener(entry.getKey(), entry.getValue());
- }
+ addMessageListenersToTransport(transport);
transport.start();
mTransports.put(associationId, transport);
Slog.i(TAG, "RawTransport is created");
@@ -148,10 +245,21 @@ public class CompanionTransportManager {
// Exchange platform info to decide which transport should be created
mTempTransport = new RawTransport(associationId, fd, mContext);
- for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
- mTempTransport.addListener(entry.getKey(), entry.getValue());
- }
- mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, this::onPlatformInfoReceived);
+ addMessageListenersToTransport(mTempTransport);
+ IOnMessageReceivedListener listener = new IOnMessageReceivedListener() {
+ @Override
+ public void onMessageReceived(int associationId, byte[] data) throws RemoteException {
+ synchronized (mTransports) {
+ onPlatformInfoReceived(associationId, data);
+ }
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+ };
+ mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, listener);
mTempTransport.start();
int sdk = Build.VERSION.SDK_INT;
@@ -163,14 +271,21 @@ public class CompanionTransportManager {
.put(release.getBytes());
// TODO: it should check if preSharedKey is given
- mTempTransport.requestForResponse(MESSAGE_REQUEST_PLATFORM_INFO, data.array());
+ try {
+ mTempTransport.sendMessage(MESSAGE_REQUEST_PLATFORM_INFO, data.array());
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to exchange platform info");
+ }
}
/**
* Depending on the remote platform info to decide which transport should be created
*/
- @GuardedBy("mTransports")
- private void onPlatformInfoReceived(byte[] data) {
+ @GuardedBy("CompanionTransportManager.this.mTransports")
+ private void onPlatformInfoReceived(int associationId, byte[] data) {
+ if (mTempTransport.getAssociationId() != associationId) {
+ return;
+ }
// TODO: it should check if preSharedKey is given
ByteBuffer buffer = ByteBuffer.wrap(data);
@@ -198,12 +313,11 @@ public class CompanionTransportManager {
Slog.i(TAG, "Creating a secure channel");
transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
mContext);
- for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
- transport.addListener(entry.getKey(), entry.getValue());
- }
+ addMessageListenersToTransport(transport);
transport.start();
}
mTransports.put(transport.getAssociationId(), transport);
+ // Doesn't need to notifyTransportsChanged here, it'll be done in attachSystemDataTransport
}
public Future<?> requestPermissionRestore(int associationId, byte[] data) {
@@ -228,4 +342,10 @@ public class CompanionTransportManager {
return enabled;
}
+
+ private void addMessageListenersToTransport(Transport transport) {
+ for (int i = 0; i < mMessageListeners.size(); i++) {
+ transport.addListener(mMessageListeners.keyAt(i), mMessageListeners.valueAt(i));
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index e984c637b44c..d69ce8909c74 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -17,10 +17,12 @@
package com.android.server.companion.transport;
import android.annotation.NonNull;
+import android.companion.IOnMessageReceivedListener;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
@@ -45,7 +47,8 @@ public abstract class Transport {
protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
- public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807086; // ?PFV
+ public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807073; // ?PFI
+ public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
@@ -59,19 +62,14 @@ public abstract class Transport {
protected final OutputStream mRemoteOut;
protected final Context mContext;
- /** Message type -> Listener */
- private final Map<Integer, Listener> mListeners;
-
/**
- * Message listener
+ * Message type -> Listener
+ *
+ * For now, the transport only supports 1 listener for each message type. If there's a need in
+ * the future to allow multiple listeners to receive callbacks for the same message type, the
+ * value of the map can be a list.
*/
- public interface Listener {
- /**
- * Called when a message is received
- * @param data data content in the message
- */
- void onDataReceived(byte[] data);
- }
+ private final Map<Integer, IOnMessageReceivedListener> mListeners;
private static boolean isRequest(int message) {
return (message & 0xFF000000) == 0x63000000;
@@ -100,7 +98,7 @@ public abstract class Transport {
* @param message Message type
* @param listener Execute when a message with the type is received
*/
- public void addListener(int message, Listener listener) {
+ public void addListener(int message, IOnMessageReceivedListener listener) {
mListeners.put(message, listener);
}
@@ -117,6 +115,13 @@ public abstract class Transport {
protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException;
+ /**
+ * Send a message
+ */
+ public void sendMessage(int message, @NonNull byte[] data) throws IOException {
+ sendMessage(message, mNextSequence.incrementAndGet(), data);
+ }
+
public Future<byte[]> requestForResponse(int message, byte[] data) {
if (DEBUG) Slog.d(TAG, "Requesting for response");
final int sequence = mNextSequence.incrementAndGet();
@@ -165,7 +170,8 @@ public abstract class Transport {
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
break;
}
- case MESSAGE_REQUEST_PLATFORM_INFO: {
+ case MESSAGE_REQUEST_PLATFORM_INFO:
+ case MESSAGE_REQUEST_CONTEXT_SYNC: {
callback(message, data);
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
break;
@@ -196,7 +202,13 @@ public abstract class Transport {
private void callback(int message, byte[] data) {
if (mListeners.containsKey(message)) {
- mListeners.get(message).onDataReceived(data);
+ try {
+ mListeners.get(message).onMessageReceived(getAssociationId(), data);
+ Slog.i(TAG, "Message 0x" + Integer.toHexString(message)
+ + " is received from associationId " + mAssociationId
+ + ", sending data length " + data.length + " to the listener.");
+ } catch (RemoteException ignored) {
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index 1c4602803426..34033e225b80 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -134,7 +134,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
new ArraySet<>();
@Nullable private final SecureWindowCallback mSecureWindowCallback;
- @Nullable private final List<String> mDisplayCategories;
+ @Nullable private final Set<String> mDisplayCategories;
private final boolean mShowTasksInHostDeviceRecents;
@@ -178,7 +178,7 @@ public class GenericWindowPolicyController extends DisplayWindowPolicyController
@NonNull ActivityBlockedCallback activityBlockedCallback,
@NonNull SecureWindowCallback secureWindowCallback,
@NonNull IntentListenerCallback intentListenerCallback,
- @NonNull List<String> displayCategories,
+ @NonNull Set<String> displayCategories,
boolean showTasksInHostDeviceRecents) {
super();
mAllowedUsers = allowedUsers;
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
index 864fe0f5edc1..7df0d861dc22 100644
--- a/services/companion/java/com/android/server/companion/virtual/SensorController.java
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -99,6 +99,9 @@ public class SensorController {
private int createSensorInternal(IBinder sensorToken, VirtualSensorConfig config)
throws SensorCreationException {
+ if (config.getType() <= 0) {
+ throw new SensorCreationException("Received an invalid virtual sensor type.");
+ }
final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
config.getType(), config.getName(),
config.getVendor() == null ? "" : config.getVendor(), config.getFlags(),
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 2d010cf66e9b..b338d89a0169 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -97,6 +97,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import java.util.function.Consumer;
@@ -830,7 +831,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
private GenericWindowPolicyController createWindowPolicyController(
- @NonNull List<String> displayCategories) {
+ @NonNull Set<String> displayCategories) {
final GenericWindowPolicyController gwpc =
new GenericWindowPolicyController(FLAG_SECURE,
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
diff --git a/services/core/java/com/android/server/LogMteState.java b/services/core/java/com/android/server/LogMteState.java
new file mode 100644
index 000000000000..410dd8339b30
--- /dev/null
+++ b/services/core/java/com/android/server/LogMteState.java
@@ -0,0 +1,53 @@
+/*
+ * 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.server;
+
+import android.app.StatsManager;
+import android.content.Context;
+import android.util.StatsEvent;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.Zygote;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+
+public class LogMteState {
+ public static void register(Context context) {
+ context.getSystemService(StatsManager.class)
+ .setPullAtomCallback(
+ FrameworkStatsLog.MTE_STATE,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ new StatsManager.StatsPullAtomCallback() {
+ @Override
+ public int onPullAtom(int atomTag, List<StatsEvent> data) {
+ if (atomTag != FrameworkStatsLog.MTE_STATE) {
+ throw new UnsupportedOperationException(
+ "Unknown tagId=" + atomTag);
+ }
+ data.add(
+ FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.MTE_STATE,
+ Zygote.nativeSupportsMemoryTagging()
+ ? FrameworkStatsLog.MTE_STATE__STATE__ON
+ : FrameworkStatsLog.MTE_STATE__STATE__OFF));
+ return StatsManager.PULL_SUCCESS;
+ }
+ });
+ }
+}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index aaa376afa139..bffa3dd79754 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1672,57 +1672,62 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
return;
}
- synchronized (mRecords) {
- String str = "notifyServiceStateForSubscriber: subId=" + subId + " phoneId=" + phoneId
- + " state=" + state;
- if (VDBG) {
- log(str);
- }
- mLocalLog.log(str);
- // for service state updates, don't notify clients when subId is invalid. This prevents
- // us from sending incorrect notifications like b/133140128
- // In the future, we can remove this logic for every notification here and add a
- // callback so listeners know when their PhoneStateListener's subId becomes invalid, but
- // for now we use the simplest fix.
- if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) {
- mServiceState[phoneId] = state;
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mRecords) {
+ String str = "notifyServiceStateForSubscriber: subId=" + subId + " phoneId="
+ + phoneId + " state=" + state;
+ if (VDBG) {
+ log(str);
+ }
+ mLocalLog.log(str);
+ // for service state updates, don't notify clients when subId is invalid. This
+ // prevents us from sending incorrect notifications like b/133140128
+ // In the future, we can remove this logic for every notification here and add a
+ // callback so listeners know when their PhoneStateListener's subId becomes invalid,
+ // but for now we use the simplest fix.
+ if (validatePhoneId(phoneId) && SubscriptionManager.isValidSubscriptionId(subId)) {
+ mServiceState[phoneId] = state;
- for (Record r : mRecords) {
- if (VDBG) {
- log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId
- + " phoneId=" + phoneId + " state=" + state);
- }
- if (r.matchTelephonyCallbackEvent(
- TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)
- && idMatch(r, subId, phoneId)) {
+ for (Record r : mRecords) {
+ if (VDBG) {
+ log("notifyServiceStateForSubscriber: r=" + r + " subId=" + subId
+ + " phoneId=" + phoneId + " state=" + state);
+ }
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_SERVICE_STATE_CHANGED)
+ && idMatch(r, subId, phoneId)) {
- try {
- ServiceState stateToSend;
- if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
- stateToSend = new ServiceState(state);
- } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
- stateToSend = state.createLocationInfoSanitizedCopy(false);
- } else {
- stateToSend = state.createLocationInfoSanitizedCopy(true);
- }
- if (DBG) {
- log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
- + " subId=" + subId + " phoneId=" + phoneId
- + " state=" + stateToSend);
+ try {
+ ServiceState stateToSend;
+ if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {
+ stateToSend = new ServiceState(state);
+ } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) {
+ stateToSend = state.createLocationInfoSanitizedCopy(false);
+ } else {
+ stateToSend = state.createLocationInfoSanitizedCopy(true);
+ }
+ if (DBG) {
+ log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
+ + " subId=" + subId + " phoneId=" + phoneId
+ + " state=" + stateToSend);
+ }
+ r.callback.onServiceStateChanged(stateToSend);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
}
- r.callback.onServiceStateChanged(stateToSend);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
}
}
+ } else {
+ log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId
+ + " or subId=" + subId);
}
- } else {
- log("notifyServiceStateForSubscriber: INVALID phoneId=" + phoneId
- + " or subId=" + subId);
+ handleRemoveListLocked();
}
- handleRemoveListLocked();
+ broadcastServiceStateChanged(state, phoneId, subId);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
}
- broadcastServiceStateChanged(state, phoneId, subId);
}
public void notifySimActivationStateChangedForPhoneId(int phoneId, int subId,
@@ -3508,13 +3513,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
- final long ident = Binder.clearCallingIdentity();
try {
mBatteryStats.notePhoneState(state.getState());
} catch (RemoteException re) {
// Can't do much
- } finally {
- Binder.restoreCallingIdentity(ident);
}
// Send the broadcast exactly once to all possible disjoint sets of apps.
@@ -3531,8 +3533,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
// - Sanitized ServiceState sent to all other apps with READ_PHONE_STATE
// - Sanitized ServiceState sent to all other apps with READ_PRIVILEGED_PHONE_STATE but not
// READ_PHONE_STATE
- if (Binder.withCleanCallingIdentity(() ->
- LocationAccessPolicy.isLocationModeEnabled(mContext, mContext.getUserId()))) {
+ if (LocationAccessPolicy.isLocationModeEnabled(mContext, mContext.getUserId())) {
Intent fullIntent = createServiceStateIntent(state, subId, phoneId, false);
mContext.createContextAsUser(UserHandle.ALL, 0).sendBroadcastMultiplePermissions(
fullIntent,
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index f90a3ce76e71..24c9e0f55ab9 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2092,6 +2092,17 @@ public final class ActiveServices {
r.appInfo.uid, r.intent.getIntent(), r, r.userId,
BackgroundStartPrivileges.NONE,
false /* isBindService */);
+ if (r.mAllowStartForeground == REASON_DENIED) {
+ Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ + " BFSL DENIED.");
+ } else {
+ if (DEBUG_SHORT_SERVICE) {
+ Slog.w(TAG_SERVICE, "FGS type change to/from SHORT_SERVICE: "
+ + " BFSL Allowed: "
+ + PowerExemptionManager.reasonCodeToString(
+ r.mAllowStartForeground));
+ }
+ }
final boolean fgsStartAllowed =
!isBgFgsRestrictionEnabledForService
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index e3f00ded026f..9e95e5fb0a40 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -995,7 +995,7 @@ final class ActivityManagerConstants extends ContentObserver {
private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
"enable_wait_for_finish_attach_application";
- private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
+ private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
/** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
public volatile boolean mEnableWaitForFinishAttachApplication =
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 432c2093bee6..4ba6854b9234 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -101,7 +101,7 @@ import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
-import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH;
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
@@ -344,6 +344,7 @@ import android.util.FeatureFlagUtils;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Log;
+import android.util.LogWriter;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -484,9 +485,11 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
@@ -5585,8 +5588,11 @@ public class ActivityManagerService extends IActivityManager.Stub
boolean isChangeEnabled = CompatChanges.isChangeEnabled(
PendingIntent.BLOCK_MUTABLE_IMPLICIT_PENDING_INTENT,
owningUid);
- logUnsafeMutableImplicitPi(packageName, resolvedTypes, owningUid, i, intent,
- isChangeEnabled);
+ String resolvedType = resolvedTypes == null
+ || i >= resolvedTypes.length ? null : resolvedTypes[i];
+ ActivityManagerUtils.logUnsafeIntentEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED,
+ owningUid, intent, resolvedType, isChangeEnabled);
if (isChangeEnabled) {
String msg = packageName + ": Targeting U+ (version "
+ Build.VERSION_CODES.UPSIDE_DOWN_CAKE + " and above) disallows"
@@ -5652,24 +5658,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- private void logUnsafeMutableImplicitPi(String packageName, String[] resolvedTypes,
- int owningUid, int i, Intent intent, boolean isChangeEnabled) {
- String[] categories = intent.getCategories() == null ? new String[0]
- : intent.getCategories().toArray(String[]::new);
- String resolvedType = resolvedTypes == null || i >= resolvedTypes.length ? null
- : resolvedTypes[i];
- FrameworkStatsLog.write(UNSAFE_INTENT_EVENT_REPORTED,
- UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED,
- owningUid,
- null,
- packageName,
- intent.getAction(),
- categories,
- resolvedType,
- intent.getScheme(),
- isChangeEnabled);
- }
-
@Override
public int sendIntentSender(IApplicationThread caller, IIntentSender target,
IBinder allowlistToken, int code, Intent intent, String resolvedType,
@@ -12909,18 +12897,9 @@ public class ActivityManagerService extends IActivityManager.Stub
boolean hasToBeExportedToMatch = platformCompat.isChangeEnabledByUid(
ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS,
callingUid);
- String[] categories = intent.getCategories() == null ? new String[0]
- : intent.getCategories().toArray(String[]::new);
- FrameworkStatsLog.write(UNSAFE_INTENT_EVENT_REPORTED,
- FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH,
- callingUid,
- componentInfo,
- callerPackage,
- intent.getAction(),
- categories,
- resolvedType,
- intent.getScheme(),
- hasToBeExportedToMatch);
+ ActivityManagerUtils.logUnsafeIntentEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH,
+ callingUid, intent, resolvedType, hasToBeExportedToMatch);
if (!hasToBeExportedToMatch) {
return;
}
@@ -18865,10 +18844,11 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void waitForBroadcastBarrier() {
- waitForBroadcastBarrier(/* printWriter= */ null, false);
+ waitForBroadcastBarrier(/* printWriter= */ null, false, false);
}
- public void waitForBroadcastBarrier(@Nullable PrintWriter pw, boolean flushBroadcastLoopers) {
+ public void waitForBroadcastBarrier(@Nullable PrintWriter pw,
+ boolean flushBroadcastLoopers, boolean flushApplicationThreads) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
if (flushBroadcastLoopers) {
BroadcastLoopers.waitForBarrier(pw);
@@ -18876,6 +18856,76 @@ public class ActivityManagerService extends IActivityManager.Stub
for (BroadcastQueue queue : mBroadcastQueues) {
queue.waitForBarrier(pw);
}
+ if (flushApplicationThreads) {
+ waitForApplicationBarrier(pw);
+ }
+ }
+
+ /**
+ * Wait for all pending {@link IApplicationThread} events to be processed in
+ * all currently running apps.
+ */
+ public void waitForApplicationBarrier(@Nullable PrintWriter pw) {
+ if (pw == null) {
+ pw = new PrintWriter(new LogWriter(Log.VERBOSE, TAG));
+ }
+
+ final CountDownLatch finishedLatch = new CountDownLatch(1);
+ final AtomicInteger pingCount = new AtomicInteger(0);
+ final AtomicInteger pongCount = new AtomicInteger(0);
+ final RemoteCallback pongCallback = new RemoteCallback((result) -> {
+ if (pongCount.incrementAndGet() == pingCount.get()) {
+ finishedLatch.countDown();
+ }
+ });
+
+ // Insert an extra "ping" as a sentinel value to guard us from finishing
+ // too quickly in parallel below
+ pingCount.incrementAndGet();
+
+ synchronized (mProcLock) {
+ final ArrayMap<String, SparseArray<ProcessRecord>> pmap =
+ mProcessList.getProcessNamesLOSP().getMap();
+ final int numProc = pmap.size();
+ for (int iProc = 0; iProc < numProc; iProc++) {
+ final SparseArray<ProcessRecord> apps = pmap.valueAt(iProc);
+ for (int iApp = 0, numApps = apps.size(); iApp < numApps; iApp++) {
+ final ProcessRecord app = apps.valueAt(iApp);
+ final IApplicationThread thread = app.getOnewayThread();
+ if (thread != null) {
+ mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
+ OomAdjuster.OOM_ADJ_REASON_NONE);
+ pingCount.incrementAndGet();
+ try {
+ thread.schedulePing(pongCallback);
+ } catch (RemoteException ignored) {
+ // When we failed to ping remote process, pretend as
+ // if we received the expected pong
+ pongCallback.sendResult(null);
+ }
+ }
+ }
+ }
+ }
+
+ // Now that we've dispatched all "ping" events above, we can send our
+ // "pong" sentinel value
+ pongCallback.sendResult(null);
+
+ // Wait for any remaining "pong" events to trickle in
+ for (int i = 0; i < 30; i++) {
+ try {
+ if (finishedLatch.await(1, TimeUnit.SECONDS)) {
+ pw.println("Finished application barriers!");
+ return;
+ } else {
+ pw.println("Waiting for application barriers, at " + pongCount.get() + " of "
+ + pingCount.get() + "...");
+ }
+ } catch (InterruptedException ignored) {
+ }
+ }
+ pw.println("Gave up waiting for application barriers!");
}
void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 02c1b8b31300..523ed6923b26 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -359,6 +359,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runWaitForBroadcastIdle(pw);
case "wait-for-broadcast-barrier":
return runWaitForBroadcastBarrier(pw);
+ case "wait-for-application-barrier":
+ return runWaitForApplicationBarrier(pw);
case "set-ignore-delivery-group-policy":
return runSetIgnoreDeliveryGroupPolicy(pw);
case "clear-ignore-delivery-group-policy":
@@ -3332,16 +3334,24 @@ final class ActivityManagerShellCommand extends ShellCommand {
int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
boolean flushBroadcastLoopers = false;
+ boolean flushApplicationThreads = false;
String opt;
while ((opt = getNextOption()) != null) {
if (opt.equals("--flush-broadcast-loopers")) {
flushBroadcastLoopers = true;
+ } else if (opt.equals("--flush-application-threads")) {
+ flushApplicationThreads = true;
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
}
}
- mInternal.waitForBroadcastBarrier(pw, flushBroadcastLoopers);
+ mInternal.waitForBroadcastBarrier(pw, flushBroadcastLoopers, flushApplicationThreads);
+ return 0;
+ }
+
+ int runWaitForApplicationBarrier(PrintWriter pw) throws RemoteException {
+ mInternal.waitForApplicationBarrier(pw);
return 0;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerUtils.java b/services/core/java/com/android/server/am/ActivityManagerUtils.java
index 9be553c49a35..01466b845a61 100644
--- a/services/core/java/com/android/server/am/ActivityManagerUtils.java
+++ b/services/core/java/com/android/server/am/ActivityManagerUtils.java
@@ -17,11 +17,13 @@ package com.android.server.am;
import android.app.ActivityThread;
import android.content.ContentResolver;
+import android.content.Intent;
import android.provider.Settings;
import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -133,4 +135,25 @@ public class ActivityManagerUtils {
public static int hashComponentNameForAtom(String shortInstanceName) {
return getUnsignedHashUnCached(shortInstanceName) ^ getAndroidIdHash();
}
+
+ /**
+ * Helper method to log an unsafe intent event.
+ */
+ public static void logUnsafeIntentEvent(int event, int callingUid,
+ Intent intent, String resolvedType, boolean blocked) {
+ String[] categories = intent.getCategories() == null ? new String[0]
+ : intent.getCategories().toArray(String[]::new);
+ String component = intent.getComponent() == null ? null
+ : intent.getComponent().flattenToString();
+ FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
+ event,
+ callingUid,
+ component,
+ intent.getPackage(),
+ intent.getAction(),
+ categories,
+ resolvedType,
+ intent.getScheme(),
+ blocked);
+ }
}
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 32d20718c192..1ba326680fc2 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -674,7 +674,7 @@ public final class AppExitInfoTracker {
break;
}
}
- } catch (IOException | IllegalArgumentException | WireTypeMismatchException e) {
+ } catch (Exception e) {
Slog.w(TAG, "Error in loading historical app exit info from persistent storage: " + e);
} finally {
if (fin != null) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0ee883f745ca..f236a961fcb6 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -402,6 +402,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
public void systemServicesReady() {
mStats.systemServicesReady(mContext);
+ mCpuWakeupStats.systemServicesReady();
mWorker.systemServicesReady();
final INetworkManagementService nms = INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
diff --git a/services/core/java/com/android/server/am/BroadcastHistory.java b/services/core/java/com/android/server/am/BroadcastHistory.java
index 6ac0e8bee58b..34658ca41356 100644
--- a/services/core/java/com/android/server/am/BroadcastHistory.java
+++ b/services/core/java/com/android/server/am/BroadcastHistory.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Intent;
import android.os.Bundle;
import android.util.TimeUtils;
@@ -26,6 +27,7 @@ import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
/**
@@ -48,6 +50,11 @@ public class BroadcastHistory {
}
/**
+ * List of broadcasts which are being delivered or yet to be delivered.
+ */
+ private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
+
+ /**
* Historical data of past broadcasts, for debugging. This is a ring buffer
* whose last element is at mHistoryNext.
*/
@@ -70,7 +77,16 @@ public class BroadcastHistory {
final long[] mSummaryHistoryDispatchTime;
final long[] mSummaryHistoryFinishTime;
- public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+ void onBroadcastEnqueuedLocked(@NonNull BroadcastRecord r) {
+ mPendingBroadcasts.add(r);
+ }
+
+ void onBroadcastFinishedLocked(@NonNull BroadcastRecord r) {
+ mPendingBroadcasts.remove(r);
+ addBroadcastToHistoryLocked(r);
+ }
+
+ public void addBroadcastToHistoryLocked(@NonNull BroadcastRecord original) {
// Note sometimes (only for sticky broadcasts?) we reuse BroadcastRecords,
// So don't change the incoming record directly.
final BroadcastRecord historyRecord = original.maybeStripForHistory();
@@ -93,7 +109,12 @@ public class BroadcastHistory {
}
@NeverCompile
- public void dumpDebug(ProtoOutputStream proto) {
+ public void dumpDebug(@NonNull ProtoOutputStream proto) {
+ for (int i = 0; i < mPendingBroadcasts.size(); ++i) {
+ final BroadcastRecord r = mPendingBroadcasts.get(i);
+ r.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCASTS);
+ }
+
int lastIndex = mHistoryNext;
int ringIndex = lastIndex;
do {
@@ -127,8 +148,20 @@ public class BroadcastHistory {
}
@NeverCompile
- public boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
- SimpleDateFormat sdf, boolean dumpAll, boolean needSep) {
+ public boolean dumpLocked(@NonNull PrintWriter pw, @Nullable String dumpPackage,
+ @NonNull String queueName, @NonNull SimpleDateFormat sdf,
+ boolean dumpAll, boolean needSep) {
+ pw.println(" Pending broadcasts:");
+ if (mPendingBroadcasts.isEmpty()) {
+ pw.println(" <empty>");
+ } else {
+ for (int idx = mPendingBroadcasts.size() - 1; idx >= 0; --idx) {
+ final BroadcastRecord r = mPendingBroadcasts.get(idx);
+ pw.print(" Broadcast #"); pw.print(idx); pw.println(":");
+ r.dump(pw, " ", sdf);
+ }
+ }
+
int i;
boolean printed = false;
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 84c03e59c658..32e5fd1ef3fd 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -1145,8 +1145,11 @@ class BroadcastProcessQueue {
pw.print(" because ");
pw.print(reasonToString(mRunnableAtReason));
pw.println();
- pw.print("mProcessCached="); pw.println(mProcessCached);
+
pw.increaseIndent();
+ dumpProcessState(pw);
+ dumpBroadcastCounts(pw);
+
if (mActive != null) {
dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
}
@@ -1167,6 +1170,49 @@ class BroadcastProcessQueue {
}
@NeverCompile
+ private void dumpProcessState(@NonNull IndentingPrintWriter pw) {
+ final StringBuilder sb = new StringBuilder();
+ if (mProcessCached) {
+ sb.append("CACHED");
+ }
+ if (mProcessInstrumented) {
+ if (sb.length() > 0) sb.append("|");
+ sb.append("INSTR");
+ }
+ if (mProcessPersistent) {
+ if (sb.length() > 0) sb.append("|");
+ sb.append("PER");
+ }
+ if (sb.length() > 0) {
+ pw.print("state:"); pw.println(sb);
+ }
+ if (runningOomAdjusted) {
+ pw.print("runningOomAdjusted:"); pw.println(runningOomAdjusted);
+ }
+ }
+
+ @NeverCompile
+ private void dumpBroadcastCounts(@NonNull IndentingPrintWriter pw) {
+ pw.print("e:"); pw.print(mCountEnqueued);
+ pw.print(" d:"); pw.print(mCountDeferred);
+ pw.print(" f:"); pw.print(mCountForeground);
+ pw.print(" fd:"); pw.print(mCountForegroundDeferred);
+ pw.print(" o:"); pw.print(mCountOrdered);
+ pw.print(" a:"); pw.print(mCountAlarm);
+ pw.print(" p:"); pw.print(mCountPrioritized);
+ pw.print(" pd:"); pw.print(mCountPrioritizedDeferred);
+ pw.print(" int:"); pw.print(mCountInteractive);
+ pw.print(" rt:"); pw.print(mCountResultTo);
+ pw.print(" ins:"); pw.print(mCountInstrumented);
+ pw.print(" m:"); pw.print(mCountManifest);
+
+ pw.print(" csi:"); pw.print(mActiveCountSinceIdle);
+ pw.print(" ccu:"); pw.print(mActiveCountConsecutiveUrgent);
+ pw.print(" ccn:"); pw.print(mActiveCountConsecutiveNormal);
+ pw.println();
+ }
+
+ @NeverCompile
private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
@NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
TimeUtils.formatDuration(record.enqueueTime, now, pw);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 841b61e8e81f..81ca267b9f63 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -90,7 +90,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -602,6 +601,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.enqueueTime = SystemClock.uptimeMillis();
r.enqueueRealTime = SystemClock.elapsedRealtime();
r.enqueueClockTime = System.currentTimeMillis();
+ mHistory.onBroadcastEnqueuedLocked(r);
ArraySet<BroadcastRecord> replacedBroadcasts = mReplacedBroadcastsCache.getAndSet(null);
if (replacedBroadcasts == null) {
@@ -825,8 +825,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (app != null && app.isInFullBackup()) {
return "isInFullBackup";
}
- if (mSkipPolicy.shouldSkip(r, receiver)) {
- return "mSkipPolicy";
+ final String skipReason = mSkipPolicy.shouldSkipMessage(r, receiver);
+ if (skipReason != null) {
+ return skipReason;
}
final Intent receiverIntent = r.getReceiverIntent(receiver);
if (receiverIntent == null) {
@@ -1100,7 +1101,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
*/
private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
@Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
- @NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) {
+ @NonNull Object receiver, @DeliveryState int newDeliveryState,
+ @NonNull String reason) {
final int cookie = traceBegin("setDeliveryState");
final int oldDeliveryState = getDeliveryState(r, index);
boolean checkFinished = false;
@@ -1108,7 +1110,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// Only apply state when we haven't already reached a terminal state;
// this is how we ignore racing timeout messages
if (!isDeliveryStateTerminal(oldDeliveryState)) {
- r.setDeliveryState(index, newDeliveryState);
+ r.setDeliveryState(index, newDeliveryState, reason);
if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
r.deferredCount--;
} else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
@@ -1659,7 +1661,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
mService.notifyBroadcastFinishedLocked(r);
r.finishTime = SystemClock.uptimeMillis();
r.nextReceiver = r.receivers.size();
- mHistory.addBroadcastToHistoryLocked(r);
+ mHistory.onBroadcastFinishedLocked(r);
BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
@@ -1833,8 +1835,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (dumpConstants) {
mConstants.dump(ipw);
}
+
if (dumpHistory) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
needSep = mHistory.dumpLocked(ipw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
}
return needSep;
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index f793c5078b5e..59f33ddb795d 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -99,6 +99,7 @@ final class BroadcastRecord extends Binder {
final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
+ final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
final int[] blockedUntilTerminalCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@@ -298,7 +299,7 @@ final class BroadcastRecord extends Binder {
pw.print(" initialSticky="); pw.println(initialSticky);
}
if (nextReceiver != 0) {
- pw.print(prefix); pw.print("nextReceiver="); pw.print(nextReceiver);
+ pw.print(prefix); pw.print("nextReceiver="); pw.println(nextReceiver);
}
if (curFilter != null) {
pw.print(prefix); pw.print("curFilter="); pw.println(curFilter);
@@ -328,6 +329,7 @@ final class BroadcastRecord extends Binder {
}
pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr);
}
+ pw.print(prefix); pw.print("terminalCount="); pw.println(terminalCount);
final int N = receivers != null ? receivers.size() : 0;
String p2 = prefix + " ";
PrintWriterPrinter printer = new PrintWriterPrinter(pw);
@@ -346,6 +348,7 @@ final class BroadcastRecord extends Binder {
TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
pw.print(' ');
}
+ pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") ");
pw.print("#"); pw.print(i); pw.print(": ");
if (o instanceof BroadcastFilter) {
pw.println(o);
@@ -356,6 +359,9 @@ final class BroadcastRecord extends Binder {
} else {
pw.println(o);
}
+ if (deliveryReasons[i] != null) {
+ pw.print(p2); pw.print("reason: "); pw.println(deliveryReasons[i]);
+ }
}
}
@@ -393,6 +399,7 @@ final class BroadcastRecord extends Binder {
options = _options;
receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
delivery = new int[_receivers != null ? _receivers.size() : 0];
+ deliveryReasons = new String[delivery.length];
deferUntilActive = options != null ? options.isDeferUntilActive() : false;
deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
@@ -448,6 +455,7 @@ final class BroadcastRecord extends Binder {
options = from.options;
receivers = from.receivers;
delivery = from.delivery;
+ deliveryReasons = from.deliveryReasons;
deferUntilActive = from.deferUntilActive;
deferredUntilActive = from.deferredUntilActive;
blockedUntilTerminalCount = from.blockedUntilTerminalCount;
@@ -609,8 +617,10 @@ final class BroadcastRecord extends Binder {
* Update the delivery state of the given {@link #receivers} index.
* Automatically updates any time measurements related to state changes.
*/
- void setDeliveryState(int index, @DeliveryState int deliveryState) {
+ void setDeliveryState(int index, @DeliveryState int deliveryState,
+ @NonNull String reason) {
delivery[index] = deliveryState;
+ deliveryReasons[index] = reason;
if (deferUntilActive) deferredUntilActive[index] = false;
switch (deliveryState) {
case DELIVERY_DELIVERED:
@@ -977,7 +987,8 @@ final class BroadcastRecord extends Binder {
if (label == null) {
label = intent.toString();
}
- mCachedToShortString = label + "/u" + userId;
+ mCachedToShortString = Integer.toHexString(System.identityHashCode(this))
+ + ":" + label + "/u" + userId;
}
return mCachedToShortString;
}
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index 82dd5c2cf2c3..6deaf7b7fe4f 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -25,6 +25,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.os.Bundle;
import android.os.Handler;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import java.util.List;
@@ -77,17 +78,23 @@ public class SameProcessApplicationThread extends IApplicationThread.Default {
@Override
public void scheduleReceiverList(List<ReceiverInfo> info) {
- for (int i = 0; i < info.size(); i++) {
- ReceiverInfo r = info.get(i);
- if (r.registered) {
- scheduleRegisteredReceiver(r.receiver, r.intent,
- r.resultCode, r.data, r.extras, r.ordered, r.sticky, r.assumeDelivered,
- r.sendingUser, r.processState, r.sendingUid, r.sendingPackage);
- } else {
- scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
- r.resultCode, r.data, r.extras, r.sync, r.assumeDelivered,
- r.sendingUser, r.processState, r.sendingUid, r.sendingPackage);
+ mHandler.post(() -> {
+ try {
+ mWrapped.scheduleReceiverList(info);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Override
+ public void schedulePing(RemoteCallback pong) {
+ mHandler.post(() -> {
+ try {
+ mWrapped.schedulePing(pong);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
}
- }
+ });
}
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index ae8ceab62896..61801177ffba 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -468,6 +468,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
long fgToken = proto.start(ServiceRecordProto.FOREGROUND);
proto.write(ServiceRecordProto.Foreground.ID, foregroundId);
foregroundNoti.dumpDebug(proto, ServiceRecordProto.Foreground.NOTIFICATION);
+ proto.write(ServiceRecordProto.Foreground.FOREGROUND_SERVICE_TYPE,
+ foregroundServiceType);
proto.end(fgToken);
}
ProtoUtils.toDuration(proto, ServiceRecordProto.CREATE_REAL_TIME, createRealTime, nowReal);
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 2d6966ad0cf8..23a384ff5d3b 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -67,8 +67,8 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
private SparseIntArray mPendingUidStates = new SparseIntArray();
private SparseIntArray mCapability = new SparseIntArray();
private SparseIntArray mPendingCapability = new SparseIntArray();
- private SparseBooleanArray mVisibleAppWidget = new SparseBooleanArray();
- private SparseBooleanArray mPendingVisibleAppWidget = new SparseBooleanArray();
+ private SparseBooleanArray mAppWidgetVisible = new SparseBooleanArray();
+ private SparseBooleanArray mPendingAppWidgetVisible = new SparseBooleanArray();
private SparseLongArray mPendingCommitTime = new SparseLongArray();
private SparseBooleanArray mPendingGone = new SparseBooleanArray();
@@ -140,7 +140,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
private int evalModeInternal(int uid, int code, int uidState, int uidCapability) {
- if (getUidVisibleAppWidget(uid) || mActivityManagerInternal.isPendingTopUid(uid)
+ if (getUidAppWidgetVisible(uid) || mActivityManagerInternal.isPendingTopUid(uid)
|| mActivityManagerInternal.isTempAllowlistedForFgsWhileInUse(uid)) {
return MODE_ALLOWED;
}
@@ -205,7 +205,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
int numUids = uidPackageNames.size();
for (int i = 0; i < numUids; i++) {
int uid = uidPackageNames.keyAt(i);
- mPendingVisibleAppWidget.put(uid, visible);
+ mPendingAppWidgetVisible.put(uid, visible);
commitUidPendingState(uid);
}
@@ -213,8 +213,6 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
@Override
public void updateUidProcState(int uid, int procState, int capability) {
- mEventLog.logUpdateUidProcState(uid, procState, capability);
-
int uidState = processStateToUidState(procState);
int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE);
@@ -226,6 +224,10 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
&& (uidState != prevUidState || capability != prevCapability))
|| (pendingStateCommitTime != 0
&& (uidState != pendingUidState || capability != pendingCapability))) {
+
+ // If this process update results in a capability or uid state change, log it. It's
+ // not interesting otherwise.
+ mEventLog.logUpdateUidProcState(uid, procState, capability);
mPendingUidStates.put(uid, uidState);
mPendingCapability.put(uid, capability);
@@ -289,9 +291,9 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
ActivityManager.printCapabilitiesFull(pw, pendingCapability);
pw.println();
}
- boolean appWidgetVisible = mVisibleAppWidget.get(uid, false);
+ boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
// if no pendingAppWidgetVisible set to appWidgetVisible to suppress output
- boolean pendingAppWidgetVisible = mPendingVisibleAppWidget.get(uid, appWidgetVisible);
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid, appWidgetVisible);
pw.print(" appWidgetVisible=");
pw.println(appWidgetVisible);
if (appWidgetVisible != pendingAppWidgetVisible) {
@@ -331,25 +333,25 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
mUidStates.get(uid, MIN_PRIORITY_UID_STATE));
int pendingCapability = mPendingCapability.get(uid,
mCapability.get(uid, PROCESS_CAPABILITY_NONE));
- boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid,
- mVisibleAppWidget.get(uid, false));
+ boolean pendingAppWidgetVisible = mPendingAppWidgetVisible.get(uid,
+ mAppWidgetVisible.get(uid, false));
int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
- boolean visibleAppWidget = mVisibleAppWidget.get(uid, false);
+ boolean appWidgetVisible = mAppWidgetVisible.get(uid, false);
if (uidState != pendingUidState
|| capability != pendingCapability
- || visibleAppWidget != pendingVisibleAppWidget) {
+ || appWidgetVisible != pendingAppWidgetVisible) {
boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
!= pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED
|| capability != pendingCapability
- || visibleAppWidget != pendingVisibleAppWidget;
+ || appWidgetVisible != pendingAppWidgetVisible;
if (foregroundChange) {
// To save on memory usage, log only interesting changes.
mEventLog.logCommitUidState(uid, pendingUidState, pendingCapability,
- pendingVisibleAppWidget);
+ pendingAppWidgetVisible, appWidgetVisible != pendingAppWidgetVisible);
}
for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
@@ -365,17 +367,17 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
if (mPendingGone.get(uid, false)) {
mUidStates.delete(uid);
mCapability.delete(uid);
- mVisibleAppWidget.delete(uid);
+ mAppWidgetVisible.delete(uid);
mPendingGone.delete(uid);
} else {
mUidStates.put(uid, pendingUidState);
mCapability.put(uid, pendingCapability);
- mVisibleAppWidget.put(uid, pendingVisibleAppWidget);
+ mAppWidgetVisible.put(uid, pendingAppWidgetVisible);
}
mPendingUidStates.delete(uid);
mPendingCapability.delete(uid);
- mPendingVisibleAppWidget.delete(uid);
+ mPendingAppWidgetVisible.delete(uid);
mPendingCommitTime.delete(uid);
}
@@ -383,21 +385,22 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
return mCapability.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE);
}
- private boolean getUidVisibleAppWidget(int uid) {
- return mVisibleAppWidget.get(uid, false);
+ private boolean getUidAppWidgetVisible(int uid) {
+ return mAppWidgetVisible.get(uid, false);
}
private static class EventLog {
- // These seems a bit too verbose and not as useful, turning off for now.
- // DCE should be able to remove most associated code.
// Memory usage: 16 * size bytes
- private static final int UPDATE_UID_PROC_STATE_LOG_MAX_SIZE = 0;
+ private static final int UPDATE_UID_PROC_STATE_LOG_MAX_SIZE = 200;
// Memory usage: 20 * size bytes
private static final int COMMIT_UID_STATE_LOG_MAX_SIZE = 200;
// Memory usage: 24 * size bytes
private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
+ private static final int APP_WIDGET_VISIBLE = 1 << 0;
+ private static final int APP_WIDGET_VISIBLE_CHANGED = 1 << 1;
+
private final DelayableExecutor mExecutor;
private final Thread mExecutorThread;
@@ -446,16 +449,18 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
mUpdateUidProcStateLogTimestamps[idx] = timestamp;
}
- void logCommitUidState(int uid, int uidState, int capability, boolean visible) {
+ void logCommitUidState(int uid, int uidState, int capability, boolean appWidgetVisible,
+ boolean appWidgetVisibleChanged) {
if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
return;
}
mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
- this, System.currentTimeMillis(), uid, uidState, capability, visible));
+ this, System.currentTimeMillis(), uid, uidState, capability, appWidgetVisible,
+ appWidgetVisibleChanged));
}
void logCommitUidStateAsync(long timestamp, int uid, int uidState, int capability,
- boolean visible) {
+ boolean appWidgetVisible, boolean appWidgetVisibleChanged) {
int idx = (mCommitUidStateLogHead + mCommitUidStateLogSize)
% COMMIT_UID_STATE_LOG_MAX_SIZE;
if (mCommitUidStateLogSize == COMMIT_UID_STATE_LOG_MAX_SIZE) {
@@ -468,7 +473,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
mCommitUidStateLog[idx][0] = uid;
mCommitUidStateLog[idx][1] = uidState;
mCommitUidStateLog[idx][2] = capability;
- mCommitUidStateLog[idx][3] = visible ? 1 : 0;
+ mCommitUidStateLog[idx][3] = 0;
+ if (appWidgetVisible) {
+ mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE;
+ }
+ if (appWidgetVisibleChanged) {
+ mCommitUidStateLog[idx][3] += APP_WIDGET_VISIBLE_CHANGED;
+ }
mCommitUidStateLogTimestamps[idx] = timestamp;
}
@@ -570,7 +581,9 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
int uid = mCommitUidStateLog[idx][0];
int uidState = mCommitUidStateLog[idx][1];
int capability = mCommitUidStateLog[idx][2];
- boolean visibleAppWidget = mCommitUidStateLog[idx][3] != 0;
+ boolean appWidgetVisible = (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE) != 0;
+ boolean appWidgetVisibleChanged =
+ (mCommitUidStateLog[idx][3] & APP_WIDGET_VISIBLE_CHANGED) != 0;
TimeUtils.dumpTime(pw, timestamp);
@@ -585,8 +598,12 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
pw.print(" capability=");
pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
- pw.print(" visibleAppWidget=");
- pw.print(visibleAppWidget);
+ pw.print(" appWidgetVisible=");
+ pw.print(appWidgetVisible);
+
+ if (appWidgetVisibleChanged) {
+ pw.print(" (changed)");
+ }
pw.println();
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0d0e5764b522..c50e275ddfa0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -8588,7 +8588,9 @@ public class AudioService extends IAudioService.Stub
if (isMutable()) {
// For call stream, align mute only when muted, not when index is set to 0
mVolumeGroupState.mute(
- forceMuteState ? mIsMuted : groupIndex == 0 || mIsMuted);
+ forceMuteState ? mIsMuted :
+ (groupIndex == 0 && !isCallStream(mStreamType))
+ || mIsMuted);
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
index 42be95b7377a..ecb7e7ca08fb 100644
--- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
+++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java
@@ -20,8 +20,13 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Intent;
import android.hardware.biometrics.IBiometricContextListener;
+import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.common.OperationReason;
+import android.hardware.biometrics.common.WakeReason;
+import android.hardware.face.FaceAuthenticateOptions;
+import android.hardware.fingerprint.FingerprintAuthenticateOptions;
+import android.os.PowerManager;
import android.view.Surface;
/**
@@ -50,12 +55,127 @@ public class OperationContextExt {
mAidlContext = context;
}
- /** Gets the subset of the context that can be shared with the HAL. */
+ /**
+ * Gets the subset of the context that can be shared with the HAL.
+ *
+ * When starting a new operation use methods like to update & fetch the context:
+ * <ul>
+ * <li>{@link #toAidlContext(FaceAuthenticateOptions)}
+ * <li>{@link #toAidlContext(FingerprintAuthenticateOptions)}
+ * </ul>
+ *
+ * Use this method for any subsequent calls to the HAL or for operations that do
+ * not accept any options.
+ *
+ * @return the underlying AIDL context
+ */
@NonNull
public OperationContext toAidlContext() {
return mAidlContext;
}
+ /**
+ * Gets the subset of the context that can be shared with the HAL and updates
+ * it with the given options.
+ *
+ * @param options authenticate options
+ * @return the underlying AIDL context
+ */
+ @NonNull
+ public OperationContext toAidlContext(@NonNull FaceAuthenticateOptions options) {
+ mAidlContext.authenticateReason = AuthenticateReason
+ .faceAuthenticateReason(getAuthReason(options));
+ mAidlContext.wakeReason = getWakeReason(options);
+
+ return mAidlContext;
+ }
+
+ /**
+ * Gets the subset of the context that can be shared with the HAL and updates
+ * it with the given options.
+ *
+ * @param options authenticate options
+ * @return the underlying AIDL context
+ */
+ @NonNull
+ public OperationContext toAidlContext(@NonNull FingerprintAuthenticateOptions options) {
+ mAidlContext.authenticateReason = AuthenticateReason
+ .fingerprintAuthenticateReason(getAuthReason(options));
+ mAidlContext.wakeReason = getWakeReason(options);
+
+ return mAidlContext;
+ }
+
+ @AuthenticateReason.Face
+ private int getAuthReason(@NonNull FaceAuthenticateOptions options) {
+ switch (options.getAuthenticateReason()) {
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP:
+ return AuthenticateReason.Face.STARTED_WAKING_UP;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN:
+ return AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_ASSISTANT_VISIBLE:
+ return AuthenticateReason.Face.ASSISTANT_VISIBLE;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN:
+ return AuthenticateReason.Face.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_NOTIFICATION_PANEL_CLICKED:
+ return AuthenticateReason.Face.NOTIFICATION_PANEL_CLICKED;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED:
+ return AuthenticateReason.Face.OCCLUDING_APP_REQUESTED;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_PICK_UP_GESTURE_TRIGGERED:
+ return AuthenticateReason.Face.PICK_UP_GESTURE_TRIGGERED;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_QS_EXPANDED:
+ return AuthenticateReason.Face.QS_EXPANDED;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_SWIPE_UP_ON_BOUNCER:
+ return AuthenticateReason.Face.SWIPE_UP_ON_BOUNCER;
+ case FaceAuthenticateOptions.AUTHENTICATE_REASON_UDFPS_POINTER_DOWN:
+ return AuthenticateReason.Face.UDFPS_POINTER_DOWN;
+ default:
+ return AuthenticateReason.Face.UNKNOWN;
+ }
+ }
+
+ @WakeReason
+ private int getWakeReason(@NonNull FaceAuthenticateOptions options) {
+ switch (options.getWakeReason()) {
+ case PowerManager.WAKE_REASON_POWER_BUTTON:
+ return WakeReason.POWER_BUTTON;
+ case PowerManager.WAKE_REASON_GESTURE:
+ return WakeReason.GESTURE;
+ case PowerManager.WAKE_REASON_WAKE_KEY:
+ return WakeReason.WAKE_KEY;
+ case PowerManager.WAKE_REASON_WAKE_MOTION:
+ return WakeReason.WAKE_MOTION;
+ case PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED:
+ return WakeReason.DISPLAY_GROUP_ADDED;
+ case PowerManager.WAKE_REASON_TAP:
+ return WakeReason.TAP;
+ case PowerManager.WAKE_REASON_LIFT:
+ return WakeReason.LIFT;
+ case PowerManager.WAKE_REASON_BIOMETRIC:
+ return WakeReason.BIOMETRIC;
+ case PowerManager.WAKE_REASON_CAMERA_LAUNCH:
+ case PowerManager.WAKE_REASON_HDMI:
+ case PowerManager.WAKE_REASON_DISPLAY_GROUP_TURNED_ON:
+ case PowerManager.WAKE_REASON_UNFOLD_DEVICE:
+ case PowerManager.WAKE_REASON_DREAM_FINISHED:
+ case PowerManager.WAKE_REASON_TILT:
+ case PowerManager.WAKE_REASON_APPLICATION:
+ case PowerManager.WAKE_REASON_PLUGGED_IN:
+ default:
+ return WakeReason.UNKNOWN;
+ }
+ }
+
+ @AuthenticateReason.Fingerprint
+ private int getAuthReason(@NonNull FingerprintAuthenticateOptions options) {
+ return AuthenticateReason.Fingerprint.UNKNOWN;
+ }
+
+ @WakeReason
+ private int getWakeReason(@NonNull FingerprintAuthenticateOptions options) {
+ return WakeReason.UNKNOWN;
+ }
+
/** {@link OperationContext#id}. */
public int getId() {
return mAidlContext.id;
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 005ad20a2d48..7b9fc36e8d61 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -74,6 +74,7 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
@Nullable
private final TaskStackListener mTaskStackListener;
private final LockoutTracker mLockoutTracker;
+ private final O mOptions;
private final boolean mIsRestricted;
private final boolean mAllowBackgroundAuthentication;
// TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update
@@ -110,6 +111,7 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
mAllowBackgroundAuthentication = allowBackgroundAuthentication;
mShouldUseLockoutTracker = lockoutTracker != null;
mSensorStrength = sensorStrength;
+ mOptions = options;
}
@LockoutTracker.LockoutMode
@@ -151,6 +153,11 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions>
return Utils.isSettings(getContext(), getOwnerString());
}
+ /** The options requested at the start of the operation. */
+ protected O getOptions() {
+ return mOptions;
+ }
+
@Override
protected boolean isCryptoOperation() {
return mOperationId != 0;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 976f1cbe1e5c..84e2fb4a5966 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -166,7 +166,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
if (session.hasContextMethods()) {
return session.getSession().authenticateWithContext(
- mOperationId, getOperationContext().toAidlContext());
+ mOperationId, getOperationContext().toAidlContext(getOptions()));
} else {
return session.getSession().authenticate(mOperationId);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index e65202dca5cd..fa23ccd482fb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -47,6 +47,7 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements
private static final String TAG = "FaceDetectClient";
private final boolean mIsStrongBiometric;
+ private final FaceAuthenticateOptions mOptions;
@Nullable private ICancellationSignal mCancellationSignal;
@Nullable private SensorPrivacyManager mSensorPrivacyManager;
@@ -74,6 +75,7 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
mSensorPrivacyManager = sensorPrivacyManager;
+ mOptions = options;
}
@Override
@@ -118,7 +120,7 @@ public class FaceDetectClient extends AcquisitionClient<AidlSession> implements
if (session.hasContextMethods()) {
return session.getSession().detectInteractionWithContext(
- getOperationContext().toAidlContext());
+ getOperationContext().toAidlContext(mOptions));
} else {
return session.getSession().detectInteraction();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 0f81f9f2660e..435e81d688bd 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -285,7 +285,7 @@ class FingerprintAuthenticationClient
if (session.hasContextMethods()) {
return session.getSession().authenticateWithContext(
- mOperationId, opContext.toAidlContext());
+ mOperationId, opContext.toAidlContext(getOptions()));
} else {
return session.getSession().authenticate(mOperationId);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 376d23187fb8..16d16fc95c5b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -48,6 +48,7 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements
private static final String TAG = "FingerprintDetectClient";
private final boolean mIsStrongBiometric;
+ private final FingerprintAuthenticateOptions mOptions;
@NonNull private final SensorOverlays mSensorOverlays;
@Nullable private ICancellationSignal mCancellationSignal;
@@ -66,6 +67,7 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements
mIsStrongBiometric = isStrongBiometric;
mSensorOverlays = new SensorOverlays(udfpsOverlayController,
null /* sideFpsController*/, udfpsOverlay);
+ mOptions = options;
}
@Override
@@ -105,7 +107,7 @@ class FingerprintDetectClient extends AcquisitionClient<AidlSession> implements
if (session.hasContextMethods()) {
return session.getSession().detectInteractionWithContext(
- getOperationContext().toAidlContext());
+ getOperationContext().toAidlContext(mOptions));
} else {
return session.getSession().detectInteraction();
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 21cc172f5908..ea157c89f675 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -128,6 +128,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.Spline;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayEventReceiver;
import android.view.DisplayInfo;
@@ -250,6 +251,7 @@ public final class DisplayManagerService extends SystemService {
private ActivityManagerInternal mActivityManagerInternal;
private ActivityManager mActivityManager;
private UidImportanceListener mUidImportanceListener = new UidImportanceListener();
+ @Nullable
private IMediaProjectionManager mProjectionService;
private DeviceStateManagerInternal mDeviceStateManager;
@GuardedBy("mSyncRoot")
@@ -1494,8 +1496,9 @@ public final class DisplayManagerService extends SystemService {
final long token = Binder.clearCallingIdentity();
try {
+ final int displayId;
synchronized (mSyncRoot) {
- final int displayId =
+ displayId =
createVirtualDisplayLocked(
callback,
projection,
@@ -1509,8 +1512,39 @@ public final class DisplayManagerService extends SystemService {
mDisplayWindowPolicyControllers.put(
displayId, Pair.create(virtualDevice, dwpc));
}
- return displayId;
}
+
+ // When calling setContentRecordingSession into the WindowManagerService, the WMS
+ // attempts to acquire a lock before executing its main body. Due to this, we need
+ // to be sure that it isn't called while the DisplayManagerService is also holding
+ // a lock, to avoid a deadlock scenario.
+ final ContentRecordingSession session =
+ virtualDisplayConfig.getContentRecordingSession();
+
+ if (displayId != Display.INVALID_DISPLAY && session != null) {
+ // Only attempt to set content recording session if there are details to set and a
+ // VirtualDisplay has been successfully constructed.
+ session.setDisplayId(displayId);
+
+ // We set the content recording session here on the server side instead of using
+ // a second AIDL call in MediaProjection. By ensuring that a virtual display has
+ // been constructed before calling setContentRecordingSession, we avoid a race
+ // condition between the DMS & WMS which could lead to the MediaProjection
+ // being pre-emptively torn down.
+ if (!mWindowManagerInternal.setContentRecordingSession(session)) {
+ // Unable to start mirroring, so tear down projection & release VirtualDisplay.
+ try {
+ getProjectionService().stopActiveProjection();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to tell MediaProjectionManagerService to stop the "
+ + "active projection", e);
+ }
+ releaseVirtualDisplayInternal(callback.asBinder());
+ return Display.INVALID_DISPLAY;
+ }
+ }
+
+ return displayId;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2126,16 +2160,24 @@ public final class DisplayManagerService extends SystemService {
autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
}
+ int conversionMode = hdrConversionMode.getConversionMode();
+ int preferredHdrType = hdrConversionMode.getPreferredHdrOutputType();
// If the HDR conversion is disabled by an app through WindowManager.LayoutParams, then
// set HDR conversion mode to HDR_CONVERSION_PASSTHROUGH.
if (mOverrideHdrConversionMode == null) {
- mSystemPreferredHdrOutputType =
- mInjector.setHdrConversionMode(hdrConversionMode.getConversionMode(),
- hdrConversionMode.getPreferredHdrOutputType(), autoHdrOutputTypes);
+ // HDR_CONVERSION_FORCE with HDR_TYPE_INVALID is used to represent forcing SDR type.
+ // But, internally SDR is selected by using passthrough mode.
+ if (conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
+ && preferredHdrType == Display.HdrCapabilities.HDR_TYPE_INVALID) {
+ conversionMode = HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+ }
} else {
- mInjector.setHdrConversionMode(mOverrideHdrConversionMode.getConversionMode(),
- mOverrideHdrConversionMode.getPreferredHdrOutputType(), null);
+ conversionMode = mOverrideHdrConversionMode.getConversionMode();
+ preferredHdrType = mOverrideHdrConversionMode.getPreferredHdrOutputType();
+ autoHdrOutputTypes = null;
}
+ mSystemPreferredHdrOutputType = mInjector.setHdrConversionMode(
+ conversionMode, preferredHdrType, autoHdrOutputTypes);
}
}
@@ -2796,8 +2838,7 @@ public final class DisplayManagerService extends SystemService {
private IMediaProjectionManager getProjectionService() {
if (mProjectionService == null) {
- IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
- mProjectionService = IMediaProjectionManager.Stub.asInterface(b);
+ mProjectionService = mInjector.getProjectionService();
}
return mProjectionService;
}
@@ -2956,6 +2997,11 @@ public final class DisplayManagerService extends SystemService {
boolean getHdrOutputConversionSupport() {
return DisplayControl.getHdrOutputConversionSupport();
}
+
+ IMediaProjectionManager getProjectionService() {
+ IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
+ return IMediaProjectionManager.Stub.asInterface(b);
+ }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 364d53ba3c10..4f7a2ba58570 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -88,7 +88,17 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
// Called with SyncRoot lock held.
public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
Context context, Handler handler, Listener listener) {
- this(syncRoot, context, handler, listener, DisplayControl::createDisplay);
+ this(syncRoot, context, handler, listener, new SurfaceControlDisplayFactory() {
+ @Override
+ public IBinder createDisplay(String name, boolean secure, float requestedRefreshRate) {
+ return DisplayControl.createDisplay(name, secure, requestedRefreshRate);
+ }
+
+ @Override
+ public void destroyDisplay(IBinder displayToken) {
+ DisplayControl.destroyDisplay(displayToken);
+ }
+ });
}
@VisibleForTesting
@@ -285,7 +295,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mUniqueIndex = uniqueIndex;
mIsDisplayOn = surface != null;
mDisplayIdToMirror = virtualDisplayConfig.getDisplayIdToMirror();
- mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroring();
+ mIsWindowManagerMirroring = virtualDisplayConfig.isWindowManagerMirroringEnabled();
}
@Override
@@ -311,7 +321,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mSurface.release();
mSurface = null;
}
- DisplayControl.destroyDisplay(getDisplayTokenLocked());
+ mSurfaceControlDisplayFactory.destroyDisplay(getDisplayTokenLocked());
if (mProjection != null && mMediaProjectionCallback != null) {
try {
mProjection.unregisterCallback(mMediaProjectionCallback);
@@ -653,5 +663,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
* @return The token reference for the display in SurfaceFlinger.
*/
IBinder createDisplay(String name, boolean secure, float requestedRefreshRate);
+
+ /**
+ * Destroy a display in SurfaceFlinger.
+ *
+ * @param displayToken The display token for the display to be destroyed.
+ */
+ void destroyDisplay(IBinder displayToken);
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
index ab84ae4c08a2..df70a32c232c 100644
--- a/services/core/java/com/android/server/dreams/DreamShellCommand.java
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -39,26 +39,24 @@ public class DreamShellCommand extends ShellCommand {
@Override
public int onCommand(String cmd) {
- final int callingUid = Binder.getCallingUid();
- if (callingUid != Process.ROOT_UID) {
- Slog.e(TAG, "Must be root before calling Dream shell commands");
- return -1;
- }
-
- if (TextUtils.isEmpty(cmd)) {
- return super.handleDefaultCommands(cmd);
- }
if (DEBUG) {
Slog.d(TAG, "onCommand:" + cmd);
}
- switch (cmd) {
- case "start-dreaming":
- return startDreaming();
- case "stop-dreaming":
- return stopDreaming();
- default:
- return super.handleDefaultCommands(cmd);
+ try {
+ switch (cmd) {
+ case "start-dreaming":
+ enforceCallerIsRoot();
+ return startDreaming();
+ case "stop-dreaming":
+ enforceCallerIsRoot();
+ return stopDreaming();
+ default:
+ return super.handleDefaultCommands(cmd);
+ }
+ } catch (SecurityException e) {
+ getOutPrintWriter().println(e);
+ return -1;
}
}
@@ -72,6 +70,12 @@ public class DreamShellCommand extends ShellCommand {
return 0;
}
+ private void enforceCallerIsRoot() {
+ if (Binder.getCallingUid() != Process.ROOT_UID) {
+ throw new SecurityException("Must be root to call Dream shell commands");
+ }
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index e0253fc3d30c..088740e83c42 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -150,6 +150,10 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
@Override
public void onInputDeviceAdded(int deviceId) {
onInputDeviceChanged(deviceId);
+ if (useNewSettingsUi()) {
+ // Force native callback to set up keyboard layout overlay for newly added keyboards
+ reloadKeyboardLayouts();
+ }
}
@Override
@@ -283,7 +287,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
final InputDeviceIdentifier identifier) {
if (useNewSettingsUi()) {
- return new KeyboardLayout[0];
+ // Provide all supported keyboard layouts since Ime info is not provided
+ return getKeyboardLayouts();
}
final String[] enabledLayoutDescriptors =
getEnabledKeyboardLayoutsForInputDevice(identifier);
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index 2174f4044ffd..c962bc4c20d8 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -295,6 +295,7 @@ public class GnssManagerService {
}
ipw.println("Capabilities: " + mGnssNative.getCapabilities());
+ ipw.println("GNSS Hardware Model Name: " + getGnssHardwareModelName());
if (mGnssStatusProvider.isSupported()) {
ipw.println("Status Provider:");
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 4f28432a20a2..cc41207eaee1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -3101,6 +3101,16 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, printWriter)) return;
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ dumpInternal(printWriter);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void dumpInternal(PrintWriter printWriter) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("Current lock settings service state:");
diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java
new file mode 100644
index 000000000000..b9c9bae8e62b
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java
@@ -0,0 +1,112 @@
+/*
+ * 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.server.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.MediaRoute2Info;
+
+/* package */ final class AudioAttributesUtils {
+
+ /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .build();
+
+ private AudioAttributesUtils() {
+ // no-op to prevent instantiation.
+ }
+
+ @MediaRoute2Info.Type
+ /* package */ static int mapToMediaRouteType(
+ @NonNull AudioDeviceAttributes audioDeviceAttributes) {
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ return MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ return MediaRoute2Info.TYPE_WIRED_HEADSET;
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ return MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_DOCK_ANALOG:
+ return MediaRoute2Info.TYPE_DOCK;
+ case AudioDeviceInfo.TYPE_HDMI:
+ return MediaRoute2Info.TYPE_HDMI;
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ return MediaRoute2Info.TYPE_USB_DEVICE;
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ case AudioDeviceInfo.TYPE_BLE_HEADSET:
+ return MediaRoute2Info.TYPE_BLE_HEADSET;
+ case AudioDeviceInfo.TYPE_HEARING_AID:
+ return MediaRoute2Info.TYPE_HEARING_AID;
+ default:
+ return MediaRoute2Info.TYPE_UNKNOWN;
+ }
+ }
+
+
+ /* package */ static boolean isDeviceOutputAttributes(
+ @Nullable AudioDeviceAttributes audioDeviceAttributes) {
+ if (audioDeviceAttributes == null) {
+ return false;
+ }
+
+ if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
+ return false;
+ }
+
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE:
+ case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_DOCK_ANALOG:
+ case AudioDeviceInfo.TYPE_HDMI:
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /* package */ static boolean isBluetoothOutputAttributes(
+ @Nullable AudioDeviceAttributes audioDeviceAttributes) {
+ if (audioDeviceAttributes == null) {
+ return false;
+ }
+
+ if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
+ return false;
+ }
+
+ switch (audioDeviceAttributes.getType()) {
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ case AudioDeviceInfo.TYPE_BLE_HEADSET:
+ case AudioDeviceInfo.TYPE_BLE_SPEAKER:
+ case AudioDeviceInfo.TYPE_HEARING_AID:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
new file mode 100644
index 000000000000..182aa6fcef02
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -0,0 +1,240 @@
+/*
+ * 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.server.media;
+
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
+import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioRoutesObserver;
+import android.media.IAudioService;
+import android.media.MediaRoute2Info;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+
+/* package */ final class AudioPoliciesDeviceRouteController implements DeviceRouteController {
+
+ private static final String TAG = "APDeviceRoutesController";
+
+ private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final AudioManager mAudioManager;
+ @NonNull
+ private final IAudioService mAudioService;
+
+ @NonNull
+ private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+ @NonNull
+ private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+
+ private int mDeviceVolume;
+
+ @NonNull
+ private MediaRoute2Info mDeviceRoute;
+ @Nullable
+ private MediaRoute2Info mSelectedRoute;
+
+ @VisibleForTesting
+ /* package */ AudioPoliciesDeviceRouteController(@NonNull Context context,
+ @NonNull AudioManager audioManager,
+ @NonNull IAudioService audioService,
+ @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(audioManager);
+ Objects.requireNonNull(audioService);
+ Objects.requireNonNull(onDeviceRouteChangedListener);
+
+ mContext = context;
+ mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
+
+ mAudioManager = audioManager;
+ mAudioService = audioService;
+
+ AudioRoutesInfo newAudioRoutes = null;
+ try {
+ newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
+ }
+
+ mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+ }
+
+ @Override
+ public synchronized boolean selectRoute(@Nullable Integer type) {
+ if (type == null) {
+ mSelectedRoute = null;
+ return true;
+ }
+
+ if (!isDeviceRouteType(type)) {
+ return false;
+ }
+
+ mSelectedRoute = createRouteFromAudioInfo(type);
+ return true;
+ }
+
+ @Override
+ @NonNull
+ public synchronized MediaRoute2Info getDeviceRoute() {
+ if (mSelectedRoute != null) {
+ return mSelectedRoute;
+ }
+ return mDeviceRoute;
+ }
+
+ @Override
+ public synchronized boolean updateVolume(int volume) {
+ if (mDeviceVolume == volume) {
+ return false;
+ }
+
+ mDeviceVolume = volume;
+
+ if (mSelectedRoute != null) {
+ mSelectedRoute = new MediaRoute2Info.Builder(mSelectedRoute)
+ .setVolume(volume)
+ .build();
+ }
+
+ mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
+ .setVolume(volume)
+ .build();
+
+ return true;
+ }
+
+ @NonNull
+ private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
+ int type = TYPE_BUILTIN_SPEAKER;
+
+ if (newRoutes != null) {
+ if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
+ type = TYPE_WIRED_HEADPHONES;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ type = TYPE_WIRED_HEADSET;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+ type = TYPE_DOCK;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
+ type = TYPE_HDMI;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
+ type = TYPE_USB_DEVICE;
+ }
+ }
+
+ return createRouteFromAudioInfo(type);
+ }
+
+ @NonNull
+ private MediaRoute2Info createRouteFromAudioInfo(@MediaRoute2Info.Type int type) {
+ int name = R.string.default_audio_route_name;
+
+ switch (type) {
+ case TYPE_WIRED_HEADPHONES:
+ case TYPE_WIRED_HEADSET:
+ name = R.string.default_audio_route_name_headphones;
+ break;
+ case TYPE_DOCK:
+ name = R.string.default_audio_route_name_dock_speakers;
+ break;
+ case TYPE_HDMI:
+ name = R.string.default_audio_route_name_external_device;
+ break;
+ case TYPE_USB_DEVICE:
+ name = R.string.default_audio_route_name_usb;
+ break;
+ }
+
+ synchronized (this) {
+ return new MediaRoute2Info.Builder(
+ DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
+ .setVolumeHandling(mAudioManager.isVolumeFixed()
+ ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+ : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolume(mDeviceVolume)
+ .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .setType(type)
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .addFeature(FEATURE_LIVE_VIDEO)
+ .addFeature(FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
+ .build();
+ }
+ }
+
+ /**
+ * Checks if the given type is a device route.
+ *
+ * <p>Device route means a route which is either built-in or wired to the current device.
+ *
+ * @param type specifies the type of the device.
+ * @return {@code true} if the device is wired or built-in and {@code false} otherwise.
+ */
+ private boolean isDeviceRouteType(@MediaRoute2Info.Type int type) {
+ switch (type) {
+ case TYPE_BUILTIN_SPEAKER:
+ case TYPE_WIRED_HEADPHONES:
+ case TYPE_WIRED_HEADSET:
+ case TYPE_DOCK:
+ case TYPE_HDMI:
+ case TYPE_USB_DEVICE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
+
+ @Override
+ public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
+ boolean isDeviceRouteChanged;
+ MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+
+ synchronized (AudioPoliciesDeviceRouteController.this) {
+ mDeviceRoute = deviceRoute;
+ isDeviceRouteChanged = mSelectedRoute == null;
+ }
+
+ if (isDeviceRouteChanged) {
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ }
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteController.java b/services/core/java/com/android/server/media/BluetoothRouteController.java
index d4a118458952..66985e0b2533 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteController.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteController.java
@@ -53,7 +53,16 @@ import java.util.Objects;
return new NoOpBluetoothRouteController();
}
- return new LegacyBluetoothRouteController(context, btAdapter, listener);
+ MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
+ boolean isUsingLegacyController = flagManager.getBoolean(
+ MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
+ true);
+
+ if (isUsingLegacyController) {
+ return new LegacyBluetoothRouteController(context, btAdapter, listener);
+ } else {
+ return new AudioPoliciesBluetoothRouteController(context, btAdapter, listener);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 8bd6416a6ddb..3875c84e618b 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -16,32 +16,14 @@
package com.android.server.media;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
-import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
-import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
-import static android.media.MediaRoute2Info.TYPE_DOCK;
-import static android.media.MediaRoute2Info.TYPE_HDMI;
-import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
-import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.media.AudioManager;
-import android.media.AudioRoutesInfo;
import android.media.IAudioRoutesObserver;
import android.media.IAudioService;
import android.media.MediaRoute2Info;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.util.Slog;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Objects;
/**
* Controls device routes.
@@ -49,145 +31,77 @@ import java.util.Objects;
* <p>A device route is a system wired route, for example, built-in speaker, wired
* headsets and headphones, dock, hdmi, or usb devices.
*
- * <p>Thread safe.
- *
* @see SystemMediaRoute2Provider
*/
-/* package */ final class DeviceRouteController {
-
- private static final String TAG = "WiredRoutesController";
-
- private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final AudioManager mAudioManager;
- @NonNull
- private final IAudioService mAudioService;
-
- @NonNull
- private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
- @NonNull
- private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
-
- private int mDeviceVolume;
- private MediaRoute2Info mDeviceRoute;
+/* package */ interface DeviceRouteController {
+ /**
+ * Returns a new instance of {@link DeviceRouteController}.
+ */
/* package */ static DeviceRouteController createInstance(@NonNull Context context,
@NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
AudioManager audioManager = context.getSystemService(AudioManager.class);
IAudioService audioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- return new DeviceRouteController(context,
- audioManager,
- audioService,
- onDeviceRouteChangedListener);
- }
-
- @VisibleForTesting
- /* package */ DeviceRouteController(@NonNull Context context,
- @NonNull AudioManager audioManager,
- @NonNull IAudioService audioService,
- @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
- Objects.requireNonNull(context);
- Objects.requireNonNull(audioManager);
- Objects.requireNonNull(audioService);
- Objects.requireNonNull(onDeviceRouteChangedListener);
-
- mContext = context;
- mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
-
- mAudioManager = audioManager;
- mAudioService = audioService;
-
- AudioRoutesInfo newAudioRoutes = null;
- try {
- newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
- } catch (RemoteException e) {
- Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
+ MediaFeatureFlagManager flagManager = MediaFeatureFlagManager.getInstance();
+ boolean isUsingLegacyController = flagManager.getBoolean(
+ MediaFeatureFlagManager.FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER,
+ true);
+
+ if (isUsingLegacyController) {
+ return new LegacyDeviceRouteController(context,
+ audioManager,
+ audioService,
+ onDeviceRouteChangedListener);
+ } else {
+ return new AudioPoliciesDeviceRouteController(context,
+ audioManager,
+ audioService,
+ onDeviceRouteChangedListener);
}
-
- mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
}
+ /**
+ * Select the route with the given built-in or wired {@link MediaRoute2Info.Type}.
+ *
+ * <p>If the type is {@code null} then unselects the route and falls back to the default device
+ * route observed from
+ * {@link com.android.server.audio.AudioService#startWatchingRoutes(IAudioRoutesObserver)}.
+ *
+ * @param type device type. May be {@code null} to unselect currently selected route.
+ * @return whether the selection succeeds. If the selection fails the state of the controller
+ * remains intact.
+ */
+ boolean selectRoute(@Nullable @MediaRoute2Info.Type Integer type);
+
+ /**
+ * Returns currently selected device (built-in or wired) route.
+ *
+ * @return non-null device route.
+ */
@NonNull
- /* package */ synchronized MediaRoute2Info getDeviceRoute() {
- return mDeviceRoute;
- }
-
- /* package */ synchronized boolean updateVolume(int volume) {
- if (mDeviceVolume == volume) {
- return false;
- }
-
- mDeviceVolume = volume;
- mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
- .setVolume(volume)
- .build();
-
- return true;
- }
-
- private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
- int name = R.string.default_audio_route_name;
- int type = TYPE_BUILTIN_SPEAKER;
-
- if (newRoutes != null) {
- if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
- type = TYPE_WIRED_HEADPHONES;
- name = com.android.internal.R.string.default_audio_route_name_headphones;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
- type = TYPE_WIRED_HEADSET;
- name = com.android.internal.R.string.default_audio_route_name_headphones;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
- type = TYPE_DOCK;
- name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
- type = TYPE_HDMI;
- name = com.android.internal.R.string.default_audio_route_name_external_device;
- } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
- type = TYPE_USB_DEVICE;
- name = com.android.internal.R.string.default_audio_route_name_usb;
- }
- }
-
- synchronized (this) {
- return new MediaRoute2Info.Builder(
- DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
- .setVolumeHandling(mAudioManager.isVolumeFixed()
- ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
- : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
- .setVolume(mDeviceVolume)
- .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
- .setType(type)
- .addFeature(FEATURE_LIVE_AUDIO)
- .addFeature(FEATURE_LIVE_VIDEO)
- .addFeature(FEATURE_LOCAL_PLAYBACK)
- .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
- .build();
- }
- }
-
- private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
- mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
- }
-
- /* package */ interface OnDeviceRouteChangedListener {
+ MediaRoute2Info getDeviceRoute();
+
+ /**
+ * Updates device route volume.
+ *
+ * @param volume specifies a volume for the device route or 0 for unknown.
+ * @return {@code true} if updated successfully and {@code false} otherwise.
+ */
+ boolean updateVolume(int volume);
+
+ /**
+ * Interface for receiving events when device route has changed.
+ */
+ interface OnDeviceRouteChangedListener {
+
+ /**
+ * Called when device route has changed.
+ *
+ * @param deviceRoute non-null device route.
+ */
void onDeviceRouteChanged(@NonNull MediaRoute2Info deviceRoute);
}
- private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
-
- @Override
- public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
- MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
- synchronized (DeviceRouteController.this) {
- mDeviceRoute = deviceRoute;
- }
- notifyDeviceRouteUpdate(deviceRoute);
- }
- }
-
}
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
new file mode 100644
index 000000000000..971d11f24b9c
--- /dev/null
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -0,0 +1,184 @@
+/*
+ * 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.server.media;
+
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
+import static android.media.MediaRoute2Info.FEATURE_LOCAL_PLAYBACK;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioRoutesObserver;
+import android.media.IAudioService;
+import android.media.MediaRoute2Info;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * Controls device routes.
+ *
+ * <p>A device route is a system wired route, for example, built-in speaker, wired
+ * headsets and headphones, dock, hdmi, or usb devices.
+ *
+ * <p>Thread safe.
+ *
+ * @see SystemMediaRoute2Provider
+ */
+/* package */ final class LegacyDeviceRouteController implements DeviceRouteController {
+
+ private static final String TAG = "LDeviceRouteController";
+
+ private static final String DEVICE_ROUTE_ID = "DEVICE_ROUTE";
+
+ @NonNull
+ private final Context mContext;
+ @NonNull
+ private final AudioManager mAudioManager;
+ @NonNull
+ private final IAudioService mAudioService;
+
+ @NonNull
+ private final OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+ @NonNull
+ private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
+
+ private int mDeviceVolume;
+ private MediaRoute2Info mDeviceRoute;
+
+ @VisibleForTesting
+ /* package */ LegacyDeviceRouteController(@NonNull Context context,
+ @NonNull AudioManager audioManager,
+ @NonNull IAudioService audioService,
+ @NonNull OnDeviceRouteChangedListener onDeviceRouteChangedListener) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(audioManager);
+ Objects.requireNonNull(audioService);
+ Objects.requireNonNull(onDeviceRouteChangedListener);
+
+ mContext = context;
+ mOnDeviceRouteChangedListener = onDeviceRouteChangedListener;
+
+ mAudioManager = audioManager;
+ mAudioService = audioService;
+
+ AudioRoutesInfo newAudioRoutes = null;
+ try {
+ newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Cannot connect to audio service to start listen to routes", e);
+ }
+
+ mDeviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+ }
+
+ @Override
+ public boolean selectRoute(@Nullable Integer type) {
+ // No-op as the controller does not support selection from the outside of the class.
+ return false;
+ }
+
+ @Override
+ @NonNull
+ public synchronized MediaRoute2Info getDeviceRoute() {
+ return mDeviceRoute;
+ }
+
+ @Override
+ public synchronized boolean updateVolume(int volume) {
+ if (mDeviceVolume == volume) {
+ return false;
+ }
+
+ mDeviceVolume = volume;
+ mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
+ .setVolume(volume)
+ .build();
+
+ return true;
+ }
+
+ private MediaRoute2Info createRouteFromAudioInfo(@Nullable AudioRoutesInfo newRoutes) {
+ int name = R.string.default_audio_route_name;
+ int type = TYPE_BUILTIN_SPEAKER;
+
+ if (newRoutes != null) {
+ if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
+ type = TYPE_WIRED_HEADPHONES;
+ name = com.android.internal.R.string.default_audio_route_name_headphones;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ type = TYPE_WIRED_HEADSET;
+ name = com.android.internal.R.string.default_audio_route_name_headphones;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+ type = TYPE_DOCK;
+ name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
+ type = TYPE_HDMI;
+ name = com.android.internal.R.string.default_audio_route_name_external_device;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
+ type = TYPE_USB_DEVICE;
+ name = com.android.internal.R.string.default_audio_route_name_usb;
+ }
+ }
+
+ synchronized (this) {
+ return new MediaRoute2Info.Builder(
+ DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
+ .setVolumeHandling(mAudioManager.isVolumeFixed()
+ ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+ : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolume(mDeviceVolume)
+ .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .setType(type)
+ .addFeature(FEATURE_LIVE_AUDIO)
+ .addFeature(FEATURE_LIVE_VIDEO)
+ .addFeature(FEATURE_LOCAL_PLAYBACK)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
+ .build();
+ }
+ }
+
+ private void notifyDeviceRouteUpdate(@NonNull MediaRoute2Info deviceRoute) {
+ mOnDeviceRouteChangedListener.onDeviceRouteChanged(deviceRoute);
+ }
+
+ private class AudioRoutesObserver extends IAudioRoutesObserver.Stub {
+
+ @Override
+ public void dispatchAudioRoutesChanged(AudioRoutesInfo newAudioRoutes) {
+ MediaRoute2Info deviceRoute = createRouteFromAudioInfo(newAudioRoutes);
+ synchronized (LegacyDeviceRouteController.this) {
+ mDeviceRoute = deviceRoute;
+ }
+ notifyDeviceRouteUpdate(deviceRoute);
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
index 723cda056694..70ee38f8f7b0 100644
--- a/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
+++ b/services/core/java/com/android/server/media/MediaFeatureFlagManager.java
@@ -32,7 +32,7 @@ import java.lang.annotation.Target;
private static final String NAMESPACE_MEDIA_BETTER_TOGETHER = "media_better_together";
@StringDef(prefix = "FEATURE_", value = {
- FEATURE_IS_USING_LEGACY_BLUETOOTH_CONTROLLER
+ FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER
})
@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })
@Retention(RetentionPolicy.SOURCE)
@@ -43,7 +43,7 @@ import java.lang.annotation.Target;
* 'Audio Strategies'-aware controller.
*/
/* package */ static final @MediaFeatureFlag String
- FEATURE_IS_USING_LEGACY_BLUETOOTH_CONTROLLER =
+ FEATURE_AUDIO_STRATEGIES_IS_USING_LEGACY_CONTROLLER =
"BluetoothRouteController__enable_legacy_bluetooth_routes_controller";
private static final MediaFeatureFlagManager sInstance = new MediaFeatureFlagManager();
@@ -52,7 +52,7 @@ import java.lang.annotation.Target;
// Empty to prevent instantiation.
}
- /* package */ MediaFeatureFlagManager getInstance() {
+ /* package */ static MediaFeatureFlagManager getInstance() {
return sInstance;
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 6619e6c3d26f..5d5c621eb3f5 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,11 +16,14 @@
package com.android.server.media;
+import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
@@ -37,6 +40,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import java.util.List;
import java.util.Objects;
/**
@@ -71,6 +75,26 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private final AudioManagerBroadcastReceiver mAudioReceiver =
new AudioManagerBroadcastReceiver();
+ private final AudioManager.OnDevicesForAttributesChangedListener
+ mOnDevicesForAttributesChangedListener =
+ new AudioManager.OnDevicesForAttributesChangedListener() {
+ @Override
+ public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
+ @NonNull List<AudioDeviceAttributes> devices) {
+ if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) {
+ return;
+ }
+
+ mHandler.post(() -> {
+ updateSelectedAudioDevice(devices);
+ notifyProviderState();
+ if (updateSessionInfosIfNeeded()) {
+ notifySessionInfoUpdated();
+ }
+ });
+ }
+ };
+
private final Object mRequestLock = new Object();
@GuardedBy("mRequestLock")
private volatile SessionCreationRequest mPendingSessionCreationRequest;
@@ -100,8 +124,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
});
});
+ mAudioManager.addOnDevicesForAttributesChangedListener(
+ AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(),
+ mOnDevicesForAttributesChangedListener);
+
// These methods below should be called after all fields are initialized, as they
// access the fields inside.
+ List<AudioDeviceAttributes> devices =
+ mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA);
+ updateSelectedAudioDevice(devices);
updateProviderState();
updateSessionInfosIfNeeded();
}
@@ -239,6 +270,26 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
}
}
+ private void updateSelectedAudioDevice(@NonNull List<AudioDeviceAttributes> devices) {
+ if (devices.isEmpty()) {
+ Slog.w(TAG, "The list of preferred devices was empty.");
+ return;
+ }
+
+ AudioDeviceAttributes audioDeviceAttributes = devices.get(0);
+
+ if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) {
+ mDeviceRouteController.selectRoute(
+ AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes));
+ mBluetoothRouteController.selectRoute(null);
+ } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) {
+ mDeviceRouteController.selectRoute(null);
+ mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress());
+ } else {
+ Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes);
+ }
+ }
+
private void updateProviderState() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 53e841d50b33..73440b7f2eec 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -892,6 +892,7 @@ abstract public class ManagedServices {
return allowedComponents;
}
+ @NonNull
protected List<String> getAllowedPackages(int userId) {
final List<String> allowedPackages = new ArrayList<>();
synchronized (mApproved) {
@@ -1181,25 +1182,6 @@ abstract public class ManagedServices {
return installed;
}
- protected Set<String> getAllowedPackages() {
- final Set<String> allowedPackages = new ArraySet<>();
- synchronized (mApproved) {
- for (int k = 0; k < mApproved.size(); k++) {
- ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.valueAt(k);
- for (int i = 0; i < allowedByType.size(); i++) {
- final ArraySet<String> allowed = allowedByType.valueAt(i);
- for (int j = 0; j < allowed.size(); j++) {
- String pkgName = getPackageName(allowed.valueAt(j));
- if (!TextUtils.isEmpty(pkgName)) {
- allowedPackages.add(pkgName);
- }
- }
- }
- }
- }
- return allowedPackages;
- }
-
private void trimApprovedListsAccordingToInstalledServices(int userId) {
synchronized (mApproved) {
final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0d394570ab8e..53b03d58beae 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2709,16 +2709,18 @@ public class NotificationManagerService extends SystemService {
}
private void sendRegisteredOnlyBroadcast(String action) {
- Intent intent = new Intent(action);
- getContext().sendBroadcastAsUser(intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
- UserHandle.ALL, null);
+ int[] userIds = mUmInternal.getProfileIds(mAmi.getCurrentUserId(), true);
+ Intent intent = new Intent(action).addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ for (int userId : userIds) {
+ getContext().sendBroadcastAsUser(intent, UserHandle.of(userId), null);
+ }
// explicitly send the broadcast to all DND packages, even if they aren't currently running
- intent.setFlags(0);
- final Set<String> dndApprovedPackages = mConditionProviders.getAllowedPackages();
- for (String pkg : dndApprovedPackages) {
- intent.setPackage(pkg);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+ for (int userId : userIds) {
+ for (String pkg : mConditionProviders.getAllowedPackages(userId)) {
+ Intent pkgIntent = new Intent(action).setPackage(pkg).setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ getContext().sendBroadcastAsUser(pkgIntent, UserHandle.of(userId));
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9329f063aee5..0d417e457509 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -136,9 +136,7 @@ public class Installer extends SystemService {
}
/**
- * @param isolated indicates if this object should <em>not</em> connect to
- * the real {@code installd}. All remote calls will be ignored
- * unless you extend this class and intercept them.
+ * @param isolated Make the installer isolated. See {@link isIsolated}.
*/
public Installer(Context context, boolean isolated) {
super(context);
@@ -153,6 +151,15 @@ public class Installer extends SystemService {
mWarnIfHeld = warnIfHeld;
}
+ /**
+ * Returns true if the installer is isolated, i.e. if this object should <em>not</em> connect to
+ * the real {@code installd}. All remote calls will be ignored unless you extend this class and
+ * intercept them.
+ */
+ public boolean isIsolated() {
+ return mIsolated;
+ }
+
@Override
public void onStart() {
if (mIsolated) {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 9e01c7af0f0b..84bee50b77b0 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -39,6 +39,7 @@ import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyCache;
import android.app.admin.DevicePolicyManager;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ActivityNotFoundException;
@@ -85,6 +86,7 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -107,6 +109,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
@@ -623,7 +626,7 @@ public class LauncherAppsService extends SystemService {
// package does not exist; should not happen
return null;
}
- return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo);
+ return new LauncherActivityInfoInternal(activityInfo, incrementalStatesInfo, user);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -676,7 +679,7 @@ public class LauncherAppsService extends SystemService {
continue;
}
results.add(new LauncherActivityInfoInternal(ri.activityInfo,
- incrementalStatesInfo));
+ incrementalStatesInfo, user));
}
return results;
}
@@ -1078,6 +1081,55 @@ public class LauncherAppsService extends SystemService {
}
@Override
+ @NonNull
+ public Map<String, LauncherActivityInfoInternal> getActivityOverrides(String callingPackage,
+ int userId) {
+ ensureShortcutPermission(callingPackage);
+ int callingUid = Binder.getCallingUid();
+ final long callerIdentity = Binder.clearCallingIdentity();
+ try {
+ Map<String, LauncherActivityInfoInternal> shortcutOverridesInfo = new ArrayMap<>();
+ UserHandle managedUserHandle = getManagedProfile(userId);
+ if (managedUserHandle == null) {
+ return shortcutOverridesInfo;
+ }
+
+ List<String> packagesToOverride =
+ DevicePolicyCache.getInstance().getLauncherShortcutOverrides();
+ for (String packageName : packagesToOverride) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ .setPackage(packageName);
+
+ List<LauncherActivityInfoInternal> possibleShortcutOverrides =
+ queryIntentLauncherActivities(
+ intent,
+ callingUid,
+ managedUserHandle
+ );
+
+ if (!possibleShortcutOverrides.isEmpty()) {
+ shortcutOverridesInfo.put(packageName, possibleShortcutOverrides.get(0));
+ }
+ }
+ return shortcutOverridesInfo;
+ } finally {
+ Binder.restoreCallingIdentity(callerIdentity);
+ }
+ }
+
+
+ @Nullable
+ private UserHandle getManagedProfile(int userId) {
+ for (UserInfo profile : mUm.getProfiles(userId)) {
+ if (profile.isManagedProfile()) {
+ return profile.getUserHandle();
+ }
+ }
+ return null;
+ }
+
+ @Override
public boolean startShortcut(String callingPackage, String packageName, String featureId,
String shortcutId, Rect sourceBounds, Bundle startActivityOptions,
int targetUserId) {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 767c0a73bc54..6a2ddc8f94b0 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -301,6 +302,15 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
throws InstallerException {
final StringBuilder builder = new StringBuilder();
+ if (useArtService()) {
+ if ((dexFlags & DEXOPT_SECONDARY_DEX) != 0) {
+ // installd may change the reference profile in place for secondary dex
+ // files, which isn't safe with the lock free approach in ART Service.
+ throw new IllegalArgumentException(
+ "Invalid OTA dexopt call for secondary dex");
+ }
+ }
+
// The current version. For v10, see b/115993344.
builder.append("10 ");
@@ -353,7 +363,6 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
PackageDexOptimizer optimizer = new OTADexoptPackageDexOptimizer(
collectingInstaller, mPackageManagerService.mInstallLock, mContext);
- // TODO(b/251903639): Allow this use of legacy dexopt code even when ART Service is enabled.
try {
optimizer.performDexOpt(pkg, pkgSetting, null /* ISAs */,
null /* CompilerStats.PackageStats */,
@@ -362,9 +371,19 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
new DexoptOptions(pkg.getPackageName(), compilationReason,
DexoptOptions.DEXOPT_BOOT_COMPLETE));
} catch (LegacyDexoptDisabledException e) {
- throw new RuntimeException(e);
+ // OTA is still allowed to use the legacy dexopt code even when ART Service is enabled.
+ // The installer is isolated and won't call into installd, and the dexopt() method is
+ // overridden to only collect the command above. Hence we shouldn't go into any code
+ // path where this exception is thrown.
+ Slog.wtf(TAG, e);
}
+ // ART Service compat note: These commands are consumed by the otapreopt binary, which uses
+ // the same legacy dexopt code as installd to invoke dex2oat. It provides output path
+ // implementations (see calculate_odex_file_path and create_cache_path in
+ // frameworks/native/cmds/installd/otapreopt.cpp) to write to different odex files than
+ // those used by ART Service in its ordinary operations, so it doesn't interfere with ART
+ // Service even when dalvik.vm.useartservice is true.
return commands;
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 0a90e7a30db6..8a4080ff029d 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
+import static com.android.server.pm.DexOptHelper.useArtService;
import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
import static com.android.server.pm.Installer.DEXOPT_ENABLE_HIDDEN_API_CHECKS;
@@ -329,8 +330,22 @@ public class PackageDexOptimizer {
String profileName = ArtManager.getProfileName(
i == 0 ? null : pkg.getSplitNames()[i - 1]);
- final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary()
- || packageUseInfo.isUsedByOtherApps(path);
+
+ final boolean isUsedByOtherApps;
+ if (options.isDexoptAsSharedLibrary()) {
+ isUsedByOtherApps = true;
+ } else if (useArtService()) {
+ // We get here when collecting dexopt commands in OTA preopt, even when ART Service
+ // is in use. packageUseInfo isn't useful in that case since the legacy dex use
+ // database hasn't been updated. So we'd have to query ART Service instead, but it
+ // doesn't provide that API. Just cop-out and bypass the cloud profile handling.
+ // That means such apps will get preopted wrong, and we'll leave it to a later
+ // background dexopt after reboot instead.
+ isUsedByOtherApps = false;
+ } else {
+ isUsedByOtherApps = packageUseInfo.isUsedByOtherApps(path);
+ }
+
String compilerFilter = getRealCompilerFilter(pkg, options.getCompilerFilter());
// If the app is used by other apps, we must not use the existing profile because it
// may contain user data, unless the profile is newly created on install.
@@ -446,6 +461,14 @@ public class PackageDexOptimizer {
private boolean prepareCloudProfile(AndroidPackage pkg, String profileName, String path,
@Nullable String dexMetadataPath) throws LegacyDexoptDisabledException {
if (dexMetadataPath != null) {
+ if (mInstaller.isIsolated()) {
+ // If the installer is isolated, the two calls to it below will return immediately,
+ // so this only short-circuits that a bit. We need to do it to avoid the
+ // LegacyDexoptDisabledException getting thrown first, when we get here during OTA
+ // preopt and ART Service is enabled.
+ return true;
+ }
+
try {
// Make sure we don't keep any existing contents.
mInstaller.deleteReferenceProfile(pkg.getPackageName(), profileName);
@@ -879,7 +902,12 @@ public class PackageDexOptimizer {
private int getDexoptNeeded(String packageName, String path, String isa, String compilerFilter,
String classLoaderContext, int profileAnalysisResult, boolean downgrade,
int dexoptFlags, String oatDir) throws LegacyDexoptDisabledException {
- Installer.checkLegacyDexoptDisabled();
+ // Allow calls from OtaDexoptService even when ART Service is in use. The installer is
+ // isolated in that case so later calls to it won't call into installd anyway.
+ if (!mInstaller.isIsolated()) {
+ Installer.checkLegacyDexoptDisabled();
+ }
+
final boolean shouldBePublic = (dexoptFlags & DEXOPT_PUBLIC) != 0;
final boolean isProfileGuidedFilter = (dexoptFlags & DEXOPT_PROFILE_GUIDED) != 0;
boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE;
@@ -948,6 +976,8 @@ public class PackageDexOptimizer {
*/
private int analyseProfiles(AndroidPackage pkg, int uid, String profileName,
String compilerFilter) throws LegacyDexoptDisabledException {
+ Installer.checkLegacyDexoptDisabled();
+
// Check if we are allowed to merge and if the compiler filter is profile guided.
if (!isProfileGuidedCompilerFilter(compilerFilter)) {
return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 928ffa718c6f..3f9a0bc89641 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -24,6 +24,7 @@ import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
+import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH;
import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
@@ -94,6 +95,7 @@ import com.android.server.EventLogTags;
import com.android.server.IntentResolver;
import com.android.server.LocalManagerRegistry;
import com.android.server.Watchdog;
+import com.android.server.am.ActivityManagerUtils;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.pkg.AndroidPackage;
@@ -1186,12 +1188,6 @@ public class PackageManagerServiceUtils {
continue;
}
- // Only enforce filter matching if target app's target SDK >= T
- if (!compat.isChangeEnabledInternal(
- ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo)) {
- continue;
- }
-
final ParsedMainComponent comp;
if (info instanceof ActivityInfo) {
if (isReceiver) {
@@ -1210,6 +1206,10 @@ public class PackageManagerServiceUtils {
continue;
}
+ // Only enforce filter matching if target app's target SDK >= T
+ final boolean enforce = compat.isChangeEnabledInternal(
+ ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS, info.applicationInfo);
+
boolean match = false;
for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
@@ -1219,14 +1219,19 @@ public class PackageManagerServiceUtils {
}
}
if (!match) {
- Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
- Slog.w(TAG, "Access blocked: " + comp.getComponentName());
- if (DEBUG_INTENT_MATCHING) {
- Slog.v(TAG, "Component intent filters:");
- comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, " "));
- Slog.v(TAG, "-----------------------------");
+ ActivityManagerUtils.logUnsafeIntentEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
+ filterCallingUid, intent, resolvedType, enforce);
+ if (enforce) {
+ Slog.w(TAG, "Intent does not match component's intent filter: " + intent);
+ Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+ if (DEBUG_INTENT_MATCHING) {
+ Slog.v(TAG, "Component intent filters:");
+ comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, " "));
+ Slog.v(TAG, "-----------------------------");
+ }
+ resolveInfos.remove(i);
}
- resolveInfos.remove(i);
}
}
}
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index a13c568f87a6..7ed10a4df1db 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTANT;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.TAG;
@@ -55,9 +56,9 @@ import android.util.Slog;
import com.android.internal.app.ResolverActivity;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
+import com.android.server.am.ActivityManagerUtils;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
@@ -130,18 +131,9 @@ final class ResolveIntentHelper {
boolean hasToBeExportedToMatch = platformCompat.isChangeEnabledByUid(
ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS,
filterCallingUid);
- String[] categories = intent.getCategories() == null ? new String[0]
- : intent.getCategories().toArray(String[]::new);
- FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
- FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH,
- filterCallingUid,
- query.get(i).getComponentInfo().getComponentName().flattenToShortString(),
- callerPackage,
- intent.getAction(),
- categories,
- resolvedType,
- intent.getScheme(),
- hasToBeExportedToMatch);
+ ActivityManagerUtils.logUnsafeIntentEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH,
+ filterCallingUid, intent, resolvedType, hasToBeExportedToMatch);
if (callback != null) {
handler.post(() -> {
try {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 45dc49dba2ca..37a59da33ad8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2977,7 +2977,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
break;
case KeyEvent.KEYCODE_H:
- if (down && event.isMetaPressed()) {
+ if (event.isMetaPressed()) {
return handleHomeShortcuts(displayId, focusedToken, event);
}
break;
@@ -3018,11 +3018,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
enterStageSplitFromRunningApp(true /* leftOrTop */);
return key_consumed;
- } else if (!down && event.isMetaPressed()) {
- boolean backKeyHandled = backKeyPress();
- if (backKeyHandled) {
- return key_consumed;
- }
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
@@ -3031,14 +3026,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return key_consumed;
}
break;
- case KeyEvent.KEYCODE_GRAVE:
- if (!down && event.isMetaPressed()) {
- boolean backKeyHandled = backKeyPress();
- if (backKeyHandled) {
- return key_consumed;
- }
- }
- break;
case KeyEvent.KEYCODE_SLASH:
if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
toggleKeyboardShortcutsMenu(event.getDeviceId());
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index f11c864edba0..bc23020e8dbe 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -12688,8 +12688,8 @@ public class BatteryStatsImpl extends BatteryStats {
energy = info.getControllerEnergyUsed();
if (!info.getUidTraffic().isEmpty()) {
for (UidTraffic traffic : info.getUidTraffic()) {
- uidRxBytes.incrementValue(traffic.getUid(), traffic.getRxBytes());
- uidTxBytes.incrementValue(traffic.getUid(), traffic.getTxBytes());
+ uidRxBytes.put(traffic.getUid(), traffic.getRxBytes());
+ uidTxBytes.put(traffic.getUid(), traffic.getTxBytes());
}
}
}
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index e8c0e5924252..b05b662dc1e8 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -22,7 +22,9 @@ import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
import android.content.Context;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.LongSparseArray;
@@ -40,6 +42,8 @@ import com.android.internal.util.IntPair;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -52,13 +56,13 @@ public class CpuWakeupStats {
private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
private static final String SUBSYSTEM_ALARM_WIFI = "Wifi";
@VisibleForTesting
- static final long WAKEUP_RETENTION_MS = 3 * 24 * 60 * 60_000; // 3 days.
- @VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
- private static final long WAKEUP_WRITE_DELAY_MS = 2 * 60 * 1000; // 2 minutes.
+ private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.MINUTES.toMillis(2);
private final Handler mHandler;
private final IrqDeviceMap mIrqDeviceMap;
+ @VisibleForTesting
+ final Config mConfig = new Config();
private final WakingActivityHistory mRecentWakingActivity = new WakingActivityHistory();
@VisibleForTesting
@@ -72,6 +76,14 @@ public class CpuWakeupStats {
mHandler = handler;
}
+ /**
+ * Called on the boot phase SYSTEM_SERVICES_READY.
+ * This ensures that DeviceConfig is ready for calls to read properties.
+ */
+ public synchronized void systemServicesReady() {
+ mConfig.register(new HandlerExecutor(mHandler));
+ }
+
private static int subsystemToStatsReason(int subsystem) {
switch (subsystem) {
case CPU_WAKEUP_SUBSYSTEM_ALARM:
@@ -126,21 +138,25 @@ public class CpuWakeupStats {
/** Notes a wakeup reason as reported by SuspendControlService to battery stats. */
public synchronized void noteWakeupTimeAndReason(long elapsedRealtime, long uptime,
String rawReason) {
- final Wakeup parsedWakeup = new Wakeup(rawReason, elapsedRealtime, uptime);
+ final Wakeup parsedWakeup = Wakeup.parseWakeup(rawReason, elapsedRealtime, uptime);
+ if (parsedWakeup == null) {
+ return;
+ }
mWakeupEvents.put(elapsedRealtime, parsedWakeup);
attemptAttributionFor(parsedWakeup);
// Assuming that wakeups always arrive in monotonically increasing elapsedRealtime order,
// we can delete all history that will not be useful in attributing future wakeups.
mRecentWakingActivity.clearAllBefore(elapsedRealtime - WAKEUP_REASON_HALF_WINDOW_MS);
- // Limit history of wakeups and their attribution to the last WAKEUP_RETENTION_MS. Note that
+ // Limit history of wakeups and their attribution to the last retentionDuration. Note that
// the last wakeup and its attribution (if computed) is always stored, even if that wakeup
- // had occurred before WAKEUP_RETENTION_MS.
- int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ // had occurred before retentionDuration.
+ final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS;
+ int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
for (int i = lastIdx; i >= 0; i--) {
mWakeupEvents.removeAt(i);
}
- lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - WAKEUP_RETENTION_MS);
+ lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration);
for (int i = lastIdx; i >= 0; i--) {
mWakeupAttribution.removeAt(i);
}
@@ -223,6 +239,9 @@ public class CpuWakeupStats {
pw.println("CPU wakeup stats:");
pw.increaseIndent();
+ mConfig.dump(pw);
+ pw.println();
+
mIrqDeviceMap.dump(pw);
pw.println();
@@ -293,7 +312,8 @@ public class CpuWakeupStats {
}
private static final class WakingActivityHistory {
- private static final long WAKING_ACTIVITY_RETENTION_MS = 3 * 60 * 60_000; // 3 hours.
+ private static final long WAKING_ACTIVITY_RETENTION_MS = TimeUnit.MINUTES.toMillis(10);
+
private SparseArray<TimeSparseArray<SparseBooleanArray>> mWakingActivity =
new SparseArray<>();
@@ -451,28 +471,25 @@ public class CpuWakeupStats {
private static final String PARSER_TAG = "CpuWakeupStats.Wakeup";
private static final String ABORT_REASON_PREFIX = "Abort";
private static final Pattern sIrqPattern = Pattern.compile("^(\\d+)\\s+(\\S+)");
-
- String mRawReason;
long mElapsedMillis;
long mUptimeMillis;
IrqDevice[] mDevices;
- Wakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
- mRawReason = rawReason;
+ private Wakeup(IrqDevice[] devices, long elapsedMillis, long uptimeMillis) {
mElapsedMillis = elapsedMillis;
mUptimeMillis = uptimeMillis;
- mDevices = parseIrqDevices(rawReason);
+ mDevices = devices;
}
- private static IrqDevice[] parseIrqDevices(String rawReason) {
+ static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
final String[] components = rawReason.split(":");
if (ArrayUtils.isEmpty(components) || components[0].startsWith(ABORT_REASON_PREFIX)) {
- // We don't support parsing aborts yet.
+ // Accounting of aborts is not supported yet.
return null;
}
int parsedDeviceCount = 0;
- IrqDevice[] parsedDevices = new IrqDevice[components.length];
+ final IrqDevice[] parsedDevices = new IrqDevice[components.length];
for (String component : components) {
final Matcher matcher = sIrqPattern.matcher(component.trim());
@@ -490,14 +507,17 @@ public class CpuWakeupStats {
parsedDevices[parsedDeviceCount++] = new IrqDevice(line, device);
}
}
- return (parsedDeviceCount > 0) ? Arrays.copyOf(parsedDevices, parsedDeviceCount) : null;
+ if (parsedDeviceCount == 0) {
+ return null;
+ }
+ return new Wakeup(Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis,
+ uptimeMillis);
}
@Override
public String toString() {
return "Wakeup{"
- + "mRawReason='" + mRawReason + '\''
- + ", mElapsedMillis=" + mElapsedMillis
+ + "mElapsedMillis=" + mElapsedMillis
+ ", mUptimeMillis=" + TimeUtils.formatDuration(mUptimeMillis)
+ ", mDevices=" + Arrays.toString(mDevices)
+ '}';
@@ -514,8 +534,56 @@ public class CpuWakeupStats {
@Override
public String toString() {
- return "IrqDevice{" + "mLine=" + mLine + ", mDevice='" + mDevice + '\'' + '}';
+ return "IrqDevice{" + "mLine=" + mLine + ", mDevice=\'" + mDevice + '\'' + '}';
}
}
}
+
+ static final class Config implements DeviceConfig.OnPropertiesChangedListener {
+ static final String KEY_WAKEUP_STATS_RETENTION_MS = "wakeup_stats_retention_ms";
+
+ private static final String[] PROPERTY_NAMES = {
+ KEY_WAKEUP_STATS_RETENTION_MS,
+ };
+
+ static final long DEFAULT_WAKEUP_STATS_RETENTION_MS = TimeUnit.DAYS.toMillis(3);
+
+ /**
+ * Wakeup stats are retained only for this duration.
+ */
+ public volatile long WAKEUP_STATS_RETENTION_MS = DEFAULT_WAKEUP_STATS_RETENTION_MS;
+
+ void register(Executor executor) {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_BATTERY_STATS,
+ executor, this);
+ onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BATTERY_STATS,
+ PROPERTY_NAMES));
+ }
+
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ for (String name : properties.getKeyset()) {
+ if (name == null) {
+ continue;
+ }
+ switch (name) {
+ case KEY_WAKEUP_STATS_RETENTION_MS:
+ WAKEUP_STATS_RETENTION_MS = properties.getLong(
+ KEY_WAKEUP_STATS_RETENTION_MS, DEFAULT_WAKEUP_STATS_RETENTION_MS);
+ break;
+ }
+ }
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Config:");
+
+ pw.increaseIndent();
+
+ pw.print(KEY_WAKEUP_STATS_RETENTION_MS, WAKEUP_STATS_RETENTION_MS);
+ pw.println();
+
+ pw.decreaseIndent();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 5bace0ebe13a..88d64df99d48 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -151,6 +151,13 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
static final long REQUEST_LISTENING_MUST_MATCH_PACKAGE = 172251878L;
+ /**
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+ static final long REQUEST_LISTENING_OTHER_USER_NOOP = 242194868L;
+
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -1888,7 +1895,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
// Check current user
if (userId != currentUser) {
- throw new IllegalArgumentException("User " + userId + " is not the current user.");
+ if (CompatChanges.isChangeEnabled(REQUEST_LISTENING_OTHER_USER_NOOP, callingUid)) {
+ return;
+ } else {
+ throw new IllegalArgumentException(
+ "User " + userId + " is not the current user.");
+ }
}
}
if (mBar != null) {
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 404ca017929d..c6684dfdb6e0 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2125,6 +2125,26 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public void notifyTvMessage(IBinder sessionToken, String type, Bundle data, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "timeShiftEnablePositionTracking");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .notifyTvMessage(type, data);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in timeShiftEnablePositionTracking", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void startRecording(IBinder sessionToken, @Nullable Uri programUri,
@Nullable Bundle params, int userId) {
final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 7a733592b30c..c9eef387eeb2 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -923,6 +923,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
null /* options */);
+ mWindowManagerInternal.setWallpaperShowWhenLocked(
+ mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
try {
@@ -1415,12 +1417,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
if (connector.mEngine != null) {
connector.mEngine.setWallpaperFlags(which);
+ mWindowManagerInternal.setWallpaperShowWhenLocked(
+ connector.mToken, (which & FLAG_LOCK) != 0);
}
} catch (RemoteException e) {
Slog.e(TAG, "Failed to update wallpaper engine flags", e);
}
- }
- );
+ });
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9def87c3d77a..41fee1c5187d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5228,8 +5228,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
}
- @VisibleForTesting
- void setVisibility(boolean visible, boolean deferHidingClient) {
+ private void setVisibility(boolean visible, boolean deferHidingClient) {
final AppTransition appTransition = getDisplayContent().mAppTransition;
// Don't set visibility to false if we were already not visible. This prevents WM from
@@ -8146,7 +8145,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
// Clear config override in #updateCompatDisplayInsets().
- onRequestedOverrideConfigurationChanged(EMPTY);
+ final int activityType = getActivityType();
+ final Configuration overrideConfig = getRequestedOverrideConfiguration();
+ overrideConfig.unset();
+ // Keep the activity type which was set when attaching to a task to prevent leaving it
+ // undefined.
+ overrideConfig.windowConfiguration.setActivityType(activityType);
+ onRequestedOverrideConfigurationChanged(overrideConfig);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index df471c56fec9..710c4af56dd1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1729,14 +1729,11 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
- Toast toast = Toast.makeText(mService.mContext,
+ UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
(ActivitySecurityModelFeatureFlags.DOC_LINK
- + (restrictActivitySwitch
- ? "returned home due to "
- : "would return home due to ")
- + callingLabel),
- Toast.LENGTH_LONG);
- UiThread.getHandler().post(toast::show);
+ + (restrictActivitySwitch ? " returned home due to "
+ : " would return home due to ")
+ + callingLabel), Toast.LENGTH_LONG).show());
}
// If the activity switch should be restricted, return home rather than the
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f73c68a42ec7..939cf1ae471b 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -888,8 +888,11 @@ public class AppTransition implements Dump {
} else {
int animAttr = mapOpenCloseTransitTypes(transit, enter);
if (animAttr != 0) {
- a = loadCustomActivityAnimation(animAttr, enter, container);
- if (a == null) {
+ final CustomAppTransition customAppTransition =
+ getCustomAppTransition(animAttr, container);
+ if (customAppTransition != null) {
+ a = loadCustomActivityAnimation(customAppTransition, enter, container);
+ } else {
if (canCustomizeAppTransition) {
a = loadAnimationAttr(lp, animAttr, transit);
} else {
@@ -911,7 +914,7 @@ public class AppTransition implements Dump {
return a;
}
- Animation loadCustomActivityAnimation(int animAttr, boolean enter, WindowContainer container) {
+ CustomAppTransition getCustomAppTransition(int animAttr, WindowContainer container) {
ActivityRecord customAnimationSource = container.asActivityRecord();
if (customAnimationSource == null) {
return null;
@@ -927,31 +930,28 @@ public class AppTransition implements Dump {
return null;
}
}
- final CustomAppTransition custom;
switch (animAttr) {
case WindowAnimation_activityOpenEnterAnimation:
case WindowAnimation_activityOpenExitAnimation:
- custom = customAnimationSource.getCustomAnimation(true /* open */);
- break;
+ return customAnimationSource.getCustomAnimation(true /* open */);
case WindowAnimation_activityCloseEnterAnimation:
case WindowAnimation_activityCloseExitAnimation:
- custom = customAnimationSource.getCustomAnimation(false /* open */);
- break;
- default:
- return null;
- }
- if (custom != null) {
- final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
- customAnimationSource.packageName, enter
- ? custom.mEnterAnim : custom.mExitAnim);
- if (a != null && custom.mBackgroundColor != 0) {
- a.setBackdropColor(custom.mBackgroundColor);
- a.setShowBackdrop(true);
- }
- return a;
+ return customAnimationSource.getCustomAnimation(false /* open */);
}
return null;
}
+ private Animation loadCustomActivityAnimation(@NonNull CustomAppTransition custom,
+ boolean enter, WindowContainer container) {
+ final ActivityRecord customAnimationSource = container.asActivityRecord();
+ final Animation a = mTransitionAnimation.loadAppTransitionAnimation(
+ customAnimationSource.packageName, enter
+ ? custom.mEnterAnim : custom.mExitAnim);
+ if (a != null && custom.mBackgroundColor != 0) {
+ a.setBackdropColor(custom.mBackgroundColor);
+ a.setShowBackdrop(true);
+ }
+ return a;
+ }
int getAppRootTaskClipMode() {
return mNextAppTransitionRequests.contains(TRANSIT_RELAUNCH)
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 0dc6e0ff1054..5c9c81300e0d 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -364,6 +364,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
/** Hides the window immediately until it is drawn in new rotation. */
void hideImmediately(WindowToken windowToken) {
+ if (isTargetToken(windowToken)) return;
final boolean original = mHideImmediately;
mHideImmediately = true;
final Operation op = new Operation(Operation.ACTION_FADE);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 947eddeaa3eb..87f5703bdbc5 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4158,13 +4158,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** @see WindowManagerInternal#onToggleImeRequested */
void onShowImeRequested() {
- if (mImeLayeringTarget == null || mInputMethodWindow == null) {
+ if (mInputMethodWindow == null) {
return;
}
// If IME window will be shown on the rotated activity, share the transformed state to
// IME window so it can compute rotated frame with rotated configuration.
- if (mImeLayeringTarget.mToken.isFixedRotationTransforming()) {
- mInputMethodWindow.mToken.linkFixedRotationTransform(mImeLayeringTarget.mToken);
+ if (mFixedRotationLaunchingApp != null) {
+ mInputMethodWindow.mToken.linkFixedRotationTransform(mFixedRotationLaunchingApp);
// Hide the window until the rotation is done to avoid intermediate artifacts if the
// parent surface of IME container is changed.
if (mAsyncRotationController != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 210a7d9538c7..c1f2b2be3ea7 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -580,6 +580,13 @@ class InsetsPolicy {
// Notification shade has control anyways, no reason to force anything.
return focusedWin;
}
+ if (focusedWin != null) {
+ final InsetsSourceProvider provider = focusedWin.getControllableInsetProvider();
+ if (provider != null && provider.getSource().getType() == Type.navigationBars()) {
+ // Navigation bar has control if it is focused.
+ return focusedWin;
+ }
+ }
if (mPolicy.isForceShowNavigationBarEnabled() && focusedWin != null
&& focusedWin.getActivityType() == ACTIVITY_TYPE_STANDARD) {
// When "force show navigation bar" is enabled, it means both force visible is true, and
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index b3b56f273f3d..e147219de4c6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -33,6 +33,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_WAKE;
@@ -2329,6 +2330,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
void applySleepTokens(boolean applyToRootTasks) {
+ boolean builtSleepTransition = false;
for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
// Set the sleeping state of the display.
final DisplayContent display = getChildAt(displayNdx);
@@ -2338,6 +2340,30 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
display.setIsSleeping(displayShouldSleep);
+ if (display.mTransitionController.isShellTransitionsEnabled() && !builtSleepTransition
+ // Only care if there are actual sleep tokens.
+ && displayShouldSleep && !display.mAllSleepTokens.isEmpty()) {
+ builtSleepTransition = true;
+ // We don't actually care about collecting anything here. We really just want
+ // this as a signal to the transition-player.
+ final Transition transition = new Transition(TRANSIT_SLEEP, 0 /* flags */,
+ display.mTransitionController, mWmService.mSyncEngine);
+ final Runnable sendSleepTransition = () -> {
+ display.mTransitionController.requestStartTransition(transition,
+ null /* trigger */, null /* remote */, null /* display */);
+ // Force playing immediately so that unrelated ops can't be collected.
+ transition.playNow();
+ };
+ if (display.mTransitionController.isCollecting()) {
+ mWmService.mSyncEngine.queueSyncSet(
+ () -> display.mTransitionController.moveToCollecting(transition),
+ sendSleepTransition);
+ } else {
+ display.mTransitionController.moveToCollecting(transition);
+ sendSleepTransition.run();
+ }
+ }
+
if (!applyToRootTasks) {
continue;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a30ab11d9f6d..bf6983b90fbb 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -218,6 +218,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final TransitionController.Logger mLogger = new TransitionController.Logger();
+ /** Whether this transition was forced to play early (eg for a SLEEP signal). */
+ private boolean mForcePlaying = false;
+
/**
* {@code false} if this transition runs purely in WMCore (meaning Shell is completely unaware
* of it). Currently, this happens before the display is ready since nothing can be seen yet.
@@ -389,6 +392,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return mState == STATE_COLLECTING || mState == STATE_STARTED;
}
+ boolean isAborted() {
+ return mState == STATE_ABORT;
+ }
+
boolean isStarted() {
return mState == STATE_STARTED;
}
@@ -997,6 +1004,11 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
void abort() {
// This calls back into itself via controller.abort, so just early return here.
if (mState == STATE_ABORT) return;
+ if (mState == STATE_PENDING) {
+ // hasn't started collecting, so can jump directly to aborted state.
+ mState = STATE_ABORT;
+ return;
+ }
if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
throw new IllegalStateException("Too late to abort. state=" + mState);
}
@@ -1007,6 +1019,27 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.dispatchLegacyAppTransitionCancelled();
}
+ /** Immediately moves this to playing even if it isn't started yet. */
+ void playNow() {
+ if (!(mState == STATE_COLLECTING || mState == STATE_STARTED)) {
+ return;
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Force Playing Transition: %d",
+ mSyncId);
+ mForcePlaying = true;
+ setAllReady();
+ if (mState == STATE_COLLECTING) {
+ start();
+ }
+ // Don't wait for actual surface-placement. We don't want anything else collected in this
+ // transition.
+ mSyncEngine.onSurfacePlacement();
+ }
+
+ boolean isForcePlaying() {
+ return mForcePlaying;
+ }
+
void setRemoteTransition(RemoteTransition remoteTransition) {
mRemoteTransition = remoteTransition;
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 79eb63462d0b..6c951bfc5f9a 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -209,6 +209,12 @@ class TransitionController {
if (mCollectingTransition != null) {
throw new IllegalStateException("Simultaneous transition collection not supported.");
}
+ if (mTransitionPlayer == null) {
+ // If sysui has been killed (by a test) or crashed, we can temporarily have no player
+ // In this case, abort the transition.
+ transition.abort();
+ return;
+ }
mCollectingTransition = transition;
// Distinguish change type because the response time is usually expected to be not too long.
final long timeoutMs =
@@ -511,6 +517,14 @@ class TransitionController {
transition.getToken(), null));
return transition;
}
+ if (mTransitionPlayer == null || transition.isAborted()) {
+ // Apparently, some tests will kill(and restart) systemui, so there is a chance that
+ // the player might be transiently null.
+ if (transition.isCollecting()) {
+ transition.abort();
+ }
+ return transition;
+ }
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Requesting StartTransition: %s", transition);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 16541c10d9db..2b848d57e2f9 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -37,6 +37,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WALLPAPER_DRAW_PENDING_TIMEOUT;
import android.annotation.Nullable;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
@@ -55,6 +56,7 @@ import android.view.WindowManager;
import android.view.animation.Animation;
import android.window.ScreenCapture;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.ProtoLog;
@@ -72,7 +74,7 @@ import java.util.function.Consumer;
class WallpaperController {
private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperController" : TAG_WM;
private WindowManagerService mService;
- private final DisplayContent mDisplayContent;
+ private DisplayContent mDisplayContent;
private final ArrayList<WallpaperWindowToken> mWallpaperTokens = new ArrayList<>();
@@ -120,9 +122,19 @@ class WallpaperController {
private boolean mShouldOffsetWallpaperCenter;
+ final boolean mEnableSeparateLockScreenEngine;
+
private final ToBooleanFunction<WindowState> mFindWallpaperTargetFunction = w -> {
if ((w.mAttrs.type == TYPE_WALLPAPER)) {
if (mFindResults.topWallpaper == null || mFindResults.resetTopWallpaper) {
+ WallpaperWindowToken token = w.mToken.asWallpaperToken();
+ if (token == null) {
+ Slog.w(TAG, "Window " + w + " has wallpaper type but not wallpaper token");
+ return false;
+ }
+ if (!token.canShowWhenLocked() && mDisplayContent.isKeyguardLocked()) {
+ return false;
+ }
mFindResults.setTopWallpaper(w);
mFindResults.resetTopWallpaper = false;
}
@@ -249,11 +261,14 @@ class WallpaperController {
WallpaperController(WindowManagerService service, DisplayContent displayContent) {
mService = service;
mDisplayContent = displayContent;
- mMaxWallpaperScale = service.mContext.getResources()
- .getFloat(com.android.internal.R.dimen.config_wallpaperMaxScale);
- mShouldOffsetWallpaperCenter = service.mContext.getResources()
- .getBoolean(
+ Resources resources = service.mContext.getResources();
+ mMaxWallpaperScale =
+ resources.getFloat(com.android.internal.R.dimen.config_wallpaperMaxScale);
+ mShouldOffsetWallpaperCenter =
+ resources.getBoolean(
com.android.internal.R.bool.config_offsetWallpaperToCenterOfLargestDisplay);
+ mEnableSeparateLockScreenEngine =
+ resources.getBoolean(R.bool.config_independentLockscreenLiveWallpaper);
}
void resetLargestDisplay(Display display) {
@@ -753,10 +768,10 @@ class WallpaperController {
result.setWallpaperTarget(wallpaperTarget);
}
- private void updateWallpaperTokens(boolean visible) {
+ private void updateWallpaperTokens(boolean visibility, boolean locked) {
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
final WallpaperWindowToken token = mWallpaperTokens.get(curTokenNdx);
- token.updateWallpaperWindows(visible);
+ token.updateWallpaperWindows(visibility && (!locked || token.canShowWhenLocked()));
}
}
@@ -794,7 +809,13 @@ class WallpaperController {
}
}
- updateWallpaperTokens(visible);
+ // Keep both wallpapers visible unless the keyguard is locked (then hide private wp)
+ updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
+
+ if (DEBUG_WALLPAPER) {
+ Slog.v(TAG, "adjustWallpaperWindows: wallpaper visibility " + visible
+ + ", lock visibility " + mDisplayContent.isKeyguardLocked());
+ }
if (visible && mLastFrozen != mFindResults.isWallpaperTargetForLetterbox) {
mLastFrozen = mFindResults.isWallpaperTargetForLetterbox;
@@ -896,7 +917,6 @@ class WallpaperController {
mWallpaperTokens.remove(token);
}
-
@VisibleForTesting
boolean canScreenshotWallpaper() {
return canScreenshotWallpaper(getTopVisibleWallpaper());
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 8708f73980c6..17ab551b5c1e 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -76,14 +76,18 @@ class WallpaperWindowToken extends WindowToken {
return;
}
mShowWhenLocked = showWhenLocked;
-
- // Move the window token to the front (private) or back (showWhenLocked). This is possible
- // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER windows.
- final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
-
- // Note: Moving all the way to the front or back breaks ordering based on addition times.
- // We should never have more than one non-animating token of each type.
- getParent().positionChildAt(position, this /* child */, false /*includingParents */);
+ if (mDisplayContent.mWallpaperController.mEnableSeparateLockScreenEngine) {
+ // Move the window token to the front (private) or back (showWhenLocked). This is
+ // possible
+ // because the DisplayArea underneath TaskDisplayArea only contains TYPE_WALLPAPER
+ // windows.
+ final int position = showWhenLocked ? POSITION_BOTTOM : POSITION_TOP;
+
+ // Note: Moving all the way to the front or back breaks ordering based on addition
+ // times.
+ // We should never have more than one non-animating token of each type.
+ getParent().positionChildAt(position, this /* child */, false /*includingParents */);
+ }
}
boolean canShowWhenLocked() {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index b9cb59a17a2e..495d7ce4e90b 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -317,7 +317,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
transition = mTransitionController.createTransition(type);
}
- if (!transition.isCollecting()) {
+ if (!transition.isCollecting() && !transition.isForcePlaying()) {
Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
+ " means Shell took too long to respond to a request. WM State may be"
+ " incorrect now, please file a bug");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 87e87b95fdfd..cf0fc0995a9b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2018,16 +2018,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/**
* Like isOnScreen(), but we don't return true if the window is part
- * of a transition that has not yet been started.
+ * of a transition but has not yet started animating.
*/
boolean isReadyForDisplay() {
- if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) {
+ if (!mHasSurface || mDestroying || !isVisibleByPolicy()) {
+ return false;
+ }
+ if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()
+ && !isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)) {
return false;
}
final boolean parentAndClientVisible = !isParentWindowHidden()
&& mViewVisibility == View.VISIBLE && mToken.isVisible();
- return mHasSurface && isVisibleByPolicy() && !mDestroying
- && (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
+ return parentAndClientVisible || isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_ALL);
}
boolean isFullyTransparent() {
@@ -2342,9 +2345,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
- mTempConfiguration.setTo(getConfiguration());
+ // Get from super to avoid using the updated global config from the override method.
+ final Configuration selfConfiguration = super.getConfiguration();
+ mTempConfiguration.setTo(selfConfiguration);
super.onConfigurationChanged(newParentConfig);
- final int diff = getConfiguration().diff(mTempConfiguration);
+ final int diff = selfConfiguration.diff(mTempConfiguration);
if (diff != 0) {
mLastConfigReportedToClient = false;
}
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 6bf18c27ee05..e84f0cc17d05 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -20,17 +20,18 @@ import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.credentials.ClearCredentialStateRequest;
+import android.credentials.CredentialProviderInfo;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -119,24 +120,23 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
private void respondToClientWithResponseAndFinish() {
Log.i(TAG, "respondToClientWithResponseAndFinish");
if (isSessionCancelled()) {
- // TODO: Differentiate btw cancelled and false
mChosenProviderMetric.setChosenProviderStatus(
- MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onSuccess();
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
mChosenProviderMetric.setChosenProviderStatus(
- MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
Log.i(TAG, "Issue while propagating the response to the client");
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -144,9 +144,8 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
Log.i(TAG, "respondToClientWithErrorAndFinish");
if (isSessionCancelled()) {
- // TODO: Differentiate btw cancelled and false
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -156,7 +155,7 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
e.printStackTrace();
}
logApiCall(ApiName.CLEAR_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
finishSession(/*propagateCancellation=*/false);
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 656e44c4bff2..7e1780d05d75 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -16,9 +16,6 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -27,17 +24,18 @@ import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialRequest;
import android.credentials.CreateCredentialResponse;
import android.credentials.CredentialManager;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ui.ProviderData;
import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -103,11 +101,11 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
setChosenMetric(componentName);
if (response != null) {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
"Invalid response");
}
@@ -143,20 +141,19 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
return;
}
if (isSessionCancelled()) {
- // TODO: Differentiate btw cancelled and false
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client: " + e.getMessage());
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -168,9 +165,8 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
return;
}
if (isSessionCancelled()) {
- // TODO: Differentiate btw cancelled and false
logApiCall(ApiName.CREATE_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -186,10 +182,10 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
private void logFailureOrUserCancel(String errorType) {
if (CreateCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
logApiCall(ApiName.CREATE_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED);
+ /* apiStatus */ ApiStatus.USER_CANCELED);
} else {
logApiCall(ApiName.CREATE_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE);
+ /* apiStatus */ ApiStatus.FAILURE);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 41ae9118d965..9c870050449c 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -36,15 +36,14 @@ import android.credentials.CreateCredentialRequest;
import android.credentials.CredentialDescription;
import android.credentials.CredentialManager;
import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.IClearCredentialStateCallback;
import android.credentials.ICreateCredentialCallback;
import android.credentials.ICredentialManager;
import android.credentials.IGetCredentialCallback;
-import android.credentials.IListEnabledProvidersCallback;
import android.credentials.ISetEnabledProvidersCallback;
-import android.credentials.ListEnabledProvidersResponse;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
import android.credentials.ui.IntentFactory;
@@ -56,7 +55,7 @@ import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -118,10 +117,11 @@ public final class CredentialManagerService
int resolvedUserId) {
List<CredentialManagerServiceImpl> services = new ArrayList<>();
List<CredentialProviderInfo> serviceInfos =
- CredentialProviderInfo.getAvailableSystemServices(
+ CredentialProviderInfoFactory.getAvailableSystemServices(
mContext,
resolvedUserId,
- /* disableSystemAppVerificationForTests= */ false);
+ /* disableSystemAppVerificationForTests= */ false,
+ new HashSet<>());
serviceInfos.forEach(
info -> {
services.add(
@@ -222,10 +222,16 @@ public final class CredentialManagerService
return hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- private void verifyPermission(String permission) throws SecurityException {
- if (!hasPermission(permission)) {
- throw new SecurityException("Caller is missing permission: " + permission);
+ private void verifyGetProvidersPermission() throws SecurityException {
+ if (hasPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)) {
+ return;
}
+
+ if (hasPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS)) {
+ return;
+ }
+
+ throw new SecurityException("Caller is missing permission: QUERY_ALL_PACKAGES or LIST_ENABLED_CREDENTIAL_PROVIDERS");
}
private boolean hasPermission(String permission) {
@@ -550,40 +556,6 @@ public final class CredentialManagerService
providerSessions.forEach(ProviderSession::invokeSession);
}
- @SuppressWarnings("GuardedBy") // ErrorProne requires listEnabledProviders
- // to be guarded by 'service.mLock', which is the same as mLock.
- @Override
- public ICancellationSignal listEnabledProviders(IListEnabledProvidersCallback callback) {
- Log.i(TAG, "listEnabledProviders");
- ICancellationSignal cancelTransport = CancellationSignal.createTransport();
-
- if (!hasWriteSecureSettingsPermission()) {
- try {
- callback.onError(
- PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR);
- } catch (RemoteException e) {
- Log.e(TAG, "Issue with invoking response: " + e.getMessage());
- }
- return cancelTransport;
- }
-
- List<String> enabledProviders = new ArrayList<>();
- runForUser(
- (service) -> {
- enabledProviders.add(service.getComponentName().flattenToString());
- });
-
- // Call the callback.
- try {
- callback.onResponse(ListEnabledProvidersResponse.create(enabledProviders));
- } catch (RemoteException e) {
- Log.i(TAG, "Issue with invoking response: " + e.getMessage());
- // TODO: Propagate failure
- }
-
- return cancelTransport;
- }
-
@Override
public void setEnabledProviders(
List<String> providers, int userId, ISetEnabledProvidersCallback callback) {
@@ -659,7 +631,7 @@ public final class CredentialManagerService
// The component name and the package name do not match.
MetricUtilities.logApiCalled(
ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.METRICS_API_STATUS_FAILURE, callingUid);
+ ApiStatus.FAILURE, callingUid);
Log.w(
TAG,
"isEnabledCredentialProviderService: Component name does not"
@@ -667,7 +639,7 @@ public final class CredentialManagerService
return false;
}
MetricUtilities.logApiCalled(ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE,
- ApiStatus.METRICS_API_STATUS_SUCCESS, callingUid);
+ ApiStatus.SUCCESS, callingUid);
return true;
}
}
@@ -677,20 +649,35 @@ public final class CredentialManagerService
}
@Override
- public List<ServiceInfo> getCredentialProviderServices(
- int userId, boolean disableSystemAppVerificationForTests, int providerFilter) {
+ public List<CredentialProviderInfo> getCredentialProviderServices(
+ int userId, int providerFilter) {
Log.i(TAG, "getCredentialProviderServices");
- verifyPermission(android.Manifest.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS);
-
- List<ServiceInfo> services = new ArrayList<>();
- List<CredentialProviderInfo> providers =
- CredentialProviderInfo.getCredentialProviderServices(
- mContext, userId, disableSystemAppVerificationForTests, providerFilter);
- for (CredentialProviderInfo p : providers) {
- services.add(p.getServiceInfo());
- }
+ verifyGetProvidersPermission();
- return services;
+ return CredentialProviderInfoFactory.getCredentialProviderServices(
+ mContext, userId, providerFilter, getEnabledProviders());
+ }
+
+ @Override
+ public List<CredentialProviderInfo> getCredentialProviderServicesForTesting(
+ int providerFilter) {
+ Log.i(TAG, "getCredentialProviderServicesForTesting");
+ verifyGetProvidersPermission();
+
+ final int userId = UserHandle.getCallingUserId();
+ return CredentialProviderInfoFactory.getCredentialProviderServicesForTesting(
+ mContext, userId, providerFilter, getEnabledProviders());
+ }
+
+ private Set<ServiceInfo> getEnabledProviders() {
+ Set<ServiceInfo> enabledProviders = new HashSet<>();
+ synchronized (mLock) {
+ runForUser(
+ (service) -> {
+ enabledProviders.add(service.getCredentialProviderInfo().getServiceInfo());
+ });
+ }
+ return enabledProviders;
}
@Override
@@ -828,11 +815,11 @@ public final class CredentialManagerService
}
private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
- return CredentialProviderInfo.getCredentialProviderServices(
+ return CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
userId,
- /* disableSystemAppVerificationForTests= */ false,
- CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS);
+ CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
+ new HashSet<>());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 546c48fe05f4..ee55a1ccc357 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -21,7 +21,8 @@ import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.service.credentials.CredentialProviderInfo;
+import android.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Log;
import android.util.Slog;
@@ -82,7 +83,7 @@ public final class CredentialManagerServiceImpl extends
Log.i(TAG, "newServiceInfoLocked with null mInfo , "
+ serviceComponent.getPackageName());
}
- mInfo = new CredentialProviderInfo(
+ mInfo = CredentialProviderInfoFactory.create(
getContext(), serviceComponent,
mUserId, /*isSystemProvider=*/false);
return mInfo.getServiceInfo();
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 2c6c0d8b4018..546c37ff95af 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.credentials.CredentialManager;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.DisabledProviderData;
import android.credentials.ui.IntentFactory;
import android.credentials.ui.ProviderData;
@@ -31,11 +32,12 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.ResultReceiver;
-import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderInfoFactory;
import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -118,11 +120,11 @@ public class CredentialManagerUi {
.map(ProviderData::getProviderFlattenedComponentName)
.collect(Collectors.toUnmodifiableSet());
Set<String> allProviders =
- CredentialProviderInfo.getCredentialProviderServices(
+ CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
mUserId,
- /* disableSystemAppVerificationForTests= */ false,
- CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY)
+ CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY,
+ new HashSet<>())
.stream()
.map(CredentialProviderInfo::getServiceInfo)
.map(ServiceInfo::getComponentName)
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index ce26c885d55f..8c6e5cea3bba 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -16,12 +16,10 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_FINAL_SUCCESS;
-
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialRequest;
import android.credentials.GetCredentialResponse;
@@ -31,11 +29,11 @@ import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.ApiName;
import com.android.server.credentials.metrics.ApiStatus;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.ArrayList;
@@ -95,11 +93,11 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
setChosenMetric(componentName);
if (response != null) {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_SUCCESS);
+ ProviderStatusForMetrics.FINAL_SUCCESS.getMetricCode());
respondToClientWithResponseAndFinish(response);
} else {
mChosenProviderMetric.setChosenProviderStatus(
- METRICS_PROVIDER_STATUS_FINAL_FAILURE);
+ ProviderStatusForMetrics.FINAL_FAILURE.getMetricCode());
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
"Invalid response from provider");
}
@@ -119,20 +117,19 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
return;
}
if (isSessionCancelled()) {
- // TODO: Differentiate btw cancelled and false
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
try {
mClientCallback.onResponse(response);
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_SUCCESS);
+ ApiStatus.SUCCESS);
} catch (RemoteException e) {
Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_FAILURE);
+ ApiStatus.FAILURE);
}
finishSession(/*propagateCancellation=*/false);
}
@@ -144,7 +141,7 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
}
if (isSessionCancelled()) {
logApiCall(ApiName.GET_CREDENTIAL, /* apiStatus */
- ApiStatus.METRICS_API_STATUS_CLIENT_CANCELED);
+ ApiStatus.CLIENT_CANCELED);
finishSession(/*propagateCancellation=*/true);
return;
}
@@ -161,10 +158,10 @@ public final class GetRequestSession extends RequestSession<GetCredentialRequest
private void logFailureOrUserCancel(String errorType) {
if (GetCredentialException.TYPE_USER_CANCELED.equals(errorType)) {
logApiCall(ApiName.GET_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_USER_CANCELED);
+ /* apiStatus */ ApiStatus.USER_CANCELED);
} else {
logApiCall(ApiName.GET_CREDENTIAL,
- /* apiStatus */ ApiStatus.METRICS_API_STATUS_FAILURE);
+ /* apiStatus */ ApiStatus.FAILURE);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index f75a9b685d10..880ae6d4201e 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -16,12 +16,6 @@
package com.android.server.credentials;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -43,20 +37,8 @@ public class MetricUtilities {
private static final String TAG = "MetricUtilities";
- private static final int DEFAULT_INT_32 = -1;
- private static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
-
- // Metrics constants TODO(b/269290341) migrate to enums eventually to improve
- protected static final int METRICS_PROVIDER_STATUS_FINAL_FAILURE =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
- protected static final int METRICS_PROVIDER_STATUS_QUERY_FAILURE =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
- protected static final int METRICS_PROVIDER_STATUS_FINAL_SUCCESS =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
- protected static final int METRICS_PROVIDER_STATUS_QUERY_SUCCESS =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
- protected static final int METRICS_PROVIDER_STATUS_UNKNOWN =
- CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+ public static final int DEFAULT_INT_32 = -1;
+ public static final int[] DEFAULT_REPEATED_INT_32 = new int[0];
/**
@@ -80,6 +62,21 @@ public class MetricUtilities {
}
/**
+ * Given any two timestamps in nanoseconds, this gets the difference and converts to
+ * milliseconds. Assumes the difference is not larger than the maximum int size.
+ *
+ * @param t2 the final timestamp
+ * @param t1 the initial timestamp
+ * @return the timestamp difference converted to microseconds
+ */
+ protected static int getMetricTimestampDifferenceMicroseconds(long t2, long t1) {
+ if (t2 - t1 > Integer.MAX_VALUE) {
+ throw new ArithmeticException("Input timestamps are too far apart and unsupported");
+ }
+ return (int) ((t2 - t1) / 1000);
+ }
+
+ /**
* The most common logging helper, handles the overall status of the API request with the
* provider status and latencies. Other versions of this method may be more useful depending
* on the situation, as this is geared towards the logging of {@link ProviderSession} types.
@@ -102,7 +99,7 @@ public class MetricUtilities {
for (var session : providerSessions) {
CandidateProviderMetric metric = session.mCandidateProviderMetric;
candidateUidList[index] = metric.getCandidateUid();
- candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMs();
+ candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
candidateStatusList[index] = metric.getProviderQueryStatus();
index++;
}
@@ -116,9 +113,11 @@ public class MetricUtilities {
/* repeated_candidate_provider_status */ candidateStatusList,
/* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
/* chosen_provider_round_trip_time_overall_microseconds */
- chosenProviderMetric.getEntireProviderLatencyMs(),
- /* chosen_provider_final_phase_microseconds */
- chosenProviderMetric.getFinalPhaseLatencyMs(),
+ chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
+ /* chosen_provider_final_phase_microseconds (backwards compat only) */
+ getMetricTimestampDifferenceMicroseconds(chosenProviderMetric
+ .getFinalFinishTimeNanoseconds(),
+ chosenProviderMetric.getUiCallEndTimeNanoseconds()),
/* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index ce9fca753a06..b7a4cd581a1a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -20,11 +20,11 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.credentials.ClearCredentialStateException;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.ClearCredentialStateRequest;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import android.util.Slog;
@@ -119,8 +119,8 @@ public final class ProviderClearSession extends ProviderSession<ClearCredential
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
+ mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
mRemoteCredentialService.onClearCredentialState(mProviderRequest, this);
- mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 97b78116bbfa..640cc3331b1a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.credentials.CreateCredentialException;
import android.credentials.CreateCredentialResponse;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
@@ -33,7 +34,6 @@ import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CreateEntry;
-import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.RemoteEntry;
import android.util.Log;
@@ -137,7 +137,8 @@ public final class ProviderCreateSession extends ProviderSession<
remoteCredentialService);
mCompleteRequest = completeCreateRequest;
setStatus(Status.PENDING);
- mProviderResponseDataHandler = new ProviderResponseDataHandler(hybridService);
+ mProviderResponseDataHandler = new ProviderResponseDataHandler(
+ ComponentName.unflattenFromString(hybridService));
}
@Override
@@ -225,8 +226,8 @@ public final class ProviderCreateSession extends ProviderSession<
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
+ mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
mRemoteCredentialService.onCreateCredential(mProviderRequest, this);
- mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
@@ -297,21 +298,23 @@ public final class ProviderCreateSession extends ProviderSession<
}
private class ProviderResponseDataHandler {
- private final ComponentName mExpectedRemoteEntryProviderService;
+ @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
@NonNull
private final Map<String, Pair<CreateEntry, Entry>> mUiCreateEntries = new HashMap<>();
@Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
- ProviderResponseDataHandler(String hybridService) {
- mExpectedRemoteEntryProviderService = ComponentName.unflattenFromString(hybridService);
+ ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
+ mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
}
public void addResponseContent(List<CreateEntry> createEntries,
RemoteEntry remoteEntry) {
createEntries.forEach(this::addCreateEntry);
- setRemoteEntry(remoteEntry);
+ if (remoteEntry != null) {
+ setRemoteEntry(remoteEntry);
+ }
}
public void addCreateEntry(CreateEntry createEntry) {
String id = generateUniqueId();
@@ -321,13 +324,13 @@ public final class ProviderCreateSession extends ProviderSession<
}
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
- if (remoteEntry == null) {
- mUiRemoteEntry = null;
+ if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
+ Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ + "checks.");
return;
}
- if (!mComponentName.equals(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it is not from the service "
- + "configured by the OEM.");
+ if (remoteEntry == null) {
+ mUiRemoteEntry = null;
return;
}
String id = generateUniqueId();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index ee813e90ddfe..07e2f877bfc0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
import android.credentials.GetCredentialException;
import android.credentials.GetCredentialResponse;
import android.credentials.ui.AuthenticationEntry;
@@ -35,7 +36,6 @@ import android.service.credentials.BeginGetCredentialRequest;
import android.service.credentials.BeginGetCredentialResponse;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialEntry;
-import android.service.credentials.CredentialProviderInfo;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.GetCredentialRequest;
import android.service.credentials.RemoteEntry;
@@ -181,7 +181,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
/** Called when the provider response has been updated by an external source. */
@Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
- Log.i(TAG, "in onProviderResponseSuccess");
onSetInitialRemoteResponse(response);
}
@@ -269,8 +268,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@Override
protected void invokeSession() {
if (mRemoteCredentialService != null) {
+ mCandidateProviderMetric.setStartQueryTimeNanoseconds(System.nanoTime());
mRemoteCredentialService.onBeginGetCredential(mProviderRequest, this);
- mCandidateProviderMetric.setStartTimeNanoseconds(System.nanoTime());
}
}
@@ -393,7 +392,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
.extractResponseContent(providerPendingIntentResponse
.getResultData());
if (response != null && !mProviderResponseDataHandler.isEmptyResponse(response)) {
- addToInitialRemoteResponse(response);
+ addToInitialRemoteResponse(response, /*isInitialResponse=*/ false);
// Additional content received is in the form of new response content.
return true;
}
@@ -401,7 +400,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
return false;
}
- private void addToInitialRemoteResponse(BeginGetCredentialResponse content) {
+ private void addToInitialRemoteResponse(BeginGetCredentialResponse content,
+ boolean isInitialResponse) {
if (content == null) {
return;
}
@@ -409,7 +409,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
content.getCredentialEntries(),
content.getActions(),
content.getAuthenticationActions(),
- content.getRemoteCredentialEntry()
+ content.getRemoteCredentialEntry(),
+ isInitialResponse
);
}
@@ -424,7 +425,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
/** Updates the response being maintained in state by this provider session. */
private void onSetInitialRemoteResponse(BeginGetCredentialResponse response) {
mProviderResponse = response;
- addToInitialRemoteResponse(response);
+ addToInitialRemoteResponse(response, /*isInitialResponse=*/true);
if (mProviderResponseDataHandler.isEmptyResponse(response)) {
updateStatusAndInvokeCallback(Status.EMPTY_RESPONSE);
return;
@@ -463,7 +464,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
}
private class ProviderResponseDataHandler {
- private final ComponentName mExpectedRemoteEntryProviderService;
+ @Nullable private final ComponentName mExpectedRemoteEntryProviderService;
@NonNull
private final Map<String, Pair<CredentialEntry, Entry>> mUiCredentialEntries =
new HashMap<>();
@@ -475,26 +476,33 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@Nullable private Pair<String, Pair<RemoteEntry, Entry>> mUiRemoteEntry = null;
- ProviderResponseDataHandler(ComponentName expectedRemoteEntryProviderService) {
+ ProviderResponseDataHandler(@Nullable ComponentName expectedRemoteEntryProviderService) {
mExpectedRemoteEntryProviderService = expectedRemoteEntryProviderService;
}
public void addResponseContent(List<CredentialEntry> credentialEntries,
List<Action> actions, List<Action> authenticationActions,
- RemoteEntry remoteEntry) {
+ RemoteEntry remoteEntry, boolean isInitialResponse) {
credentialEntries.forEach(this::addCredentialEntry);
actions.forEach(this::addAction);
authenticationActions.forEach(
authenticationAction -> addAuthenticationAction(authenticationAction,
AuthenticationEntry.STATUS_LOCKED));
- setRemoteEntry(remoteEntry);
+ // In the query phase, it is likely most providers will return a null remote entry
+ // so no need to invoke the setter since it adds the overhead of checking for the
+ // hybrid permission, and then sets an already null value to null.
+ // If this is not the query phase, e.g. response after a locked entry is unlocked
+ // then it is valid for the provider to remove the remote entry, and so we allow
+ // them to set it to null.
+ if (remoteEntry != null || !isInitialResponse) {
+ setRemoteEntry(remoteEntry);
+ }
}
public void addCredentialEntry(CredentialEntry credentialEntry) {
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
- setUpFillInIntent(credentialEntry
- .getBeginGetCredentialOption().getId()));
+ setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
}
@@ -524,12 +532,13 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
}
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
- if (remoteEntry == null) {
+ if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
+ Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ + " checks.");
return;
}
- if (!mComponentName.equals(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it is not from the service "
- + "configured by the OEM.");
+ if (remoteEntry == null) {
+ mUiRemoteEntry = null;
return;
}
String id = generateUniqueId();
@@ -538,6 +547,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
mUiRemoteEntry = new Pair<>(generateUniqueId(), new Pair<>(remoteEntry, entry));
}
+
+
public GetCredentialProviderData toGetCredentialProviderData() {
return new GetCredentialProviderData.Builder(
mComponentName.flattenToString()).setActionChips(prepareActionEntries())
@@ -571,7 +582,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
return credEntries;
}
-
private Entry prepareRemoteEntry() {
if (mUiRemoteEntry == null || mUiRemoteEntry.first == null
|| mUiRemoteEntry.second == null) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index ecddcf30f88d..a85769572972 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -16,22 +16,23 @@
package com.android.server.credentials;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_FAILURE;
-import static com.android.server.credentials.MetricUtilities.METRICS_PROVIDER_STATUS_QUERY_SUCCESS;
-
+import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.credentials.Credential;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.server.credentials.metrics.CandidateProviderMetric;
+import com.android.server.credentials.metrics.ProviderStatusForMetrics;
import java.util.UUID;
@@ -202,9 +203,11 @@ public abstract class ProviderSession<T, R>
mCandidateProviderMetric
.setQueryFinishTimeNanoseconds(System.nanoTime());
if (isTerminatingStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_FAILURE);
+ mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_FAILURE
+ .getMetricCode());
} else if (isCompletionStatus(status)) {
- mCandidateProviderMetric.setProviderQueryStatus(METRICS_PROVIDER_STATUS_QUERY_SUCCESS);
+ mCandidateProviderMetric.setProviderQueryStatus(ProviderStatusForMetrics.QUERY_SUCCESS
+ .getMetricCode());
}
}
@@ -228,6 +231,39 @@ public abstract class ProviderSession<T, R>
return mProviderResponse;
}
+ protected boolean enforceRemoteEntryRestrictions(
+ @Nullable ComponentName expectedRemoteEntryProviderService) {
+ // Check if the service is the one set by the OEM. If not silently reject this entry
+ if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
+ Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ + "configured by the OEM.");
+ return false;
+ }
+ // Check if the service has the hybrid permission .If not, silently reject this entry.
+ // This check is in addition to the permission check happening in the provider's process.
+ try {
+ ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
+ mComponentName.getPackageName(),
+ PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY));
+ if (appInfo != null
+ && mContext.checkPermission(
+ Manifest.permission.PROVIDE_REMOTE_CREDENTIALS,
+ /*pId=*/-1, appInfo.uid) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+ } catch (SecurityException e) {
+ Log.i(TAG, "Error getting info for "
+ + mComponentName.flattenToString() + ": " + e.getMessage());
+ return false;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.i(TAG, "Error getting info for "
+ + mComponentName.flattenToString() + ": " + e.getMessage());
+ return false;
+ }
+ Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail");
+ return false;
+ }
+
/** Should be overridden to prepare, and stores state for {@link ProviderData} to be
* shown on the UI. */
@Nullable protected abstract ProviderData prepareUiData();
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 702261ea43f5..ff4e3b680131 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -121,8 +121,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
ProviderCallbacks<BeginGetCredentialResponse> callback) {
Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
- AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
- new AtomicReference<>();
CompletableFuture<BeginGetCredentialResponse> connectThenExecute = postAsync(service -> {
CompletableFuture<BeginGetCredentialResponse> getCredentials =
@@ -134,7 +132,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
new IBeginGetCredentialCallback.Stub() {
@Override
public void onSuccess(BeginGetCredentialResponse response) {
- Log.i(TAG, "In onSuccess in RemoteCredentialService");
getCredentials.complete(response);
}
@@ -147,22 +144,15 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
new GetCredentialException(errorType, errorMsg));
}
});
- CompletableFuture<BeginGetCredentialResponse> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellationSignal);
- } else {
- cancellationSink.set(cancellationSignal);
- }
+ cancellationSink.set(cancellationSignal);
return getCredentials;
} finally {
Binder.restoreCallingIdentity(originalCallingUidToken);
}
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
- futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
-
return cancellationSink.get();
}
@@ -178,8 +168,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
ProviderCallbacks<BeginCreateCredentialResponse> callback) {
Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
- AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
- new AtomicReference<>();
CompletableFuture<BeginCreateCredentialResponse> connectThenExecute =
postAsync(service -> {
@@ -205,19 +193,13 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
new CreateCredentialException(errorType, errorMsg));
}
});
- CompletableFuture<BeginCreateCredentialResponse> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellationSignal);
- } else {
- cancellationSink.set(cancellationSignal);
- }
+ cancellationSink.set(cancellationSignal);
return createCredentialFuture;
} finally {
Binder.restoreCallingIdentity(originalCallingUidToken);
}
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
- futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
@@ -236,7 +218,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
ProviderCallbacks<Void> callback) {
Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
- AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
CompletableFuture<Void> connectThenExecute =
postAsync(service -> {
@@ -263,19 +244,13 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
errorMsg));
}
});
- CompletableFuture<Void> future = futureRef.get();
- if (future != null && future.isCancelled()) {
- dispatchCancellationSignal(cancellationSignal);
- } else {
- cancellationSink.set(cancellationSignal);
- }
+ cancellationSink.set(cancellationSignal);
return clearCredentialFuture;
} finally {
Binder.restoreCallingIdentity(originalCallingUidToken);
}
}).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
- futureRef.set(connectThenExecute);
connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
handleExecutionResponse(result, error, cancellationSink, callback)));
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 86e05cf62f5f..c1f35d0f8195 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
+import android.credentials.CredentialProviderInfo;
import android.credentials.ui.ProviderData;
import android.credentials.ui.UserSelectionDialogResult;
import android.os.Binder;
@@ -30,7 +31,6 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.service.credentials.CallingAppInfo;
-import android.service.credentials.CredentialProviderInfo;
import android.util.Log;
import com.android.internal.R;
@@ -78,7 +78,8 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
//TODO improve design to allow grouped metrics per request
protected final String mHybridService;
- @NonNull protected RequestSessionStatus mRequestSessionStatus =
+ @NonNull
+ protected RequestSessionStatus mRequestSessionStatus =
RequestSessionStatus.IN_PROGRESS;
/** The status in which a given request session is. */
@@ -213,6 +214,7 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
/**
* Called by RequestSession's upon chosen metric determination.
+ *
* @param componentName the componentName to associate with a provider
*/
protected void setChosenMetric(ComponentName componentName) {
@@ -220,8 +222,8 @@ abstract class RequestSession<T, U> implements CredentialManagerUi.CredentialMan
.mCandidateProviderMetric;
mChosenProviderMetric.setChosenUid(metric.getCandidateUid());
mChosenProviderMetric.setFinalFinishTimeNanoseconds(System.nanoTime());
- mChosenProviderMetric.setQueryFinishTimeNanoseconds(
- metric.getQueryFinishTimeNanoseconds());
- mChosenProviderMetric.setStartTimeNanoseconds(metric.getStartTimeNanoseconds());
+ mChosenProviderMetric.setQueryPhaseLatencyMicroseconds(
+ metric.getQueryLatencyMicroseconds());
+ mChosenProviderMetric.setQueryStartTimeNanoseconds(metric.getStartQueryTimeNanoseconds());
}
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
index 36a1f2df6d24..22cab707387d 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ApiStatus.java
@@ -22,11 +22,11 @@ import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API
import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED;
public enum ApiStatus {
- METRICS_API_STATUS_SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS),
- METRICS_API_STATUS_FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE),
- METRICS_API_STATUS_CLIENT_CANCELED(
+ SUCCESS(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_SUCCESS),
+ FAILURE(CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_FAILURE),
+ CLIENT_CANCELED(
CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_CLIENT_CANCELED),
- METRICS_API_STATUS_USER_CANCELED(
+ USER_CANCELED(
CREDENTIAL_MANAGER_API_CALLED__API_STATUS__API_STATUS_USER_CANCELED);
private final int mInnerMetricCode;
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
index acfb4a4e3e39..9f438ecc1146 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateProviderMetric.java
@@ -18,63 +18,72 @@ package com.android.server.credentials.metrics;
/**
* The central candidate provider metric object that mimics our defined metric setup.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
+ * TODO(b/270403549) - iterate on this in V3+
*/
public class CandidateProviderMetric {
+ private static final String TAG = "CandidateProviderMetric";
private int mCandidateUid = -1;
- private long mStartTimeNanoseconds = -1;
+
+ // Raw timestamp in nanoseconds, will be converted to microseconds for logging
+
+ private long mStartQueryTimeNanoseconds = -1;
private long mQueryFinishTimeNanoseconds = -1;
private int mProviderQueryStatus = -1;
- public CandidateProviderMetric(long startTime, long queryFinishTime, int providerQueryStatus,
- int candidateUid) {
- this.mStartTimeNanoseconds = startTime;
- this.mQueryFinishTimeNanoseconds = queryFinishTime;
- this.mProviderQueryStatus = providerQueryStatus;
- this.mCandidateUid = candidateUid;
+ public CandidateProviderMetric() {
}
- public CandidateProviderMetric(){}
+ /* ---------- Latencies ---------- */
- public void setStartTimeNanoseconds(long startTimeNanoseconds) {
- this.mStartTimeNanoseconds = startTimeNanoseconds;
+ public void setStartQueryTimeNanoseconds(long startQueryTimeNanoseconds) {
+ this.mStartQueryTimeNanoseconds = startQueryTimeNanoseconds;
}
public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) {
this.mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
}
- public void setProviderQueryStatus(int providerQueryStatus) {
- this.mProviderQueryStatus = providerQueryStatus;
+ public long getStartQueryTimeNanoseconds() {
+ return this.mStartQueryTimeNanoseconds;
}
- public void setCandidateUid(int candidateUid) {
- this.mCandidateUid = candidateUid;
+ public long getQueryFinishTimeNanoseconds() {
+ return this.mQueryFinishTimeNanoseconds;
}
- public long getStartTimeNanoseconds() {
- return this.mStartTimeNanoseconds;
+ /**
+ * Returns the latency in microseconds for the query phase.
+ */
+ public int getQueryLatencyMicroseconds() {
+ return (int) ((this.getQueryFinishTimeNanoseconds()
+ - this.getStartQueryTimeNanoseconds()) / 1000);
}
- public long getQueryFinishTimeNanoseconds() {
- return this.mQueryFinishTimeNanoseconds;
+ // TODO (in direct next dependent CL, so this is transient) - add reference timestamp in micro
+ // seconds for this too.
+
+ /* ------------- Provider Query Status ------------ */
+
+ public void setProviderQueryStatus(int providerQueryStatus) {
+ this.mProviderQueryStatus = providerQueryStatus;
}
public int getProviderQueryStatus() {
return this.mProviderQueryStatus;
}
- public int getCandidateUid() {
- return this.mCandidateUid;
- }
+ /* -------------- Candidate Uid ---------------- */
- /**
- * Returns the latency in microseconds for the query phase.
- */
- public int getQueryLatencyMs() {
- return (int) ((this.getQueryFinishTimeNanoseconds()
- - this.getStartTimeNanoseconds()) / 1000);
+ public void setCandidateUid(int candidateUid) {
+ this.mCandidateUid = candidateUid;
}
+ public int getCandidateUid() {
+ return this.mCandidateUid;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
index c4d0b3c7254d..03102558d21b 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/ChosenProviderMetric.java
@@ -16,18 +16,42 @@
package com.android.server.credentials.metrics;
+import android.util.Log;
+
+import com.android.server.credentials.MetricUtilities;
+
/**
* The central chosen provider metric object that mimics our defined metric setup.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
+ * TODO(b/270403549) - iterate on this in V3+
*/
public class ChosenProviderMetric {
+ // TODO(b/270403549) - applies elsewhere, likely removed or replaced with a count-index (1,2,3)
+ private static final String TAG = "ChosenProviderMetric";
private int mChosenUid = -1;
- private long mStartTimeNanoseconds = -1;
- private long mQueryFinishTimeNanoseconds = -1;
+
+ // Latency figures typically fed in from prior CandidateProviderMetric
+
+ private int mPreQueryPhaseLatencyMicroseconds = -1;
+ private int mQueryPhaseLatencyMicroseconds = -1;
+
+ // Timestamps kept in raw nanoseconds. Expected to be converted to microseconds from using
+ // reference 'mServiceBeganTimeNanoseconds' during metric log point.
+
+ private long mServiceBeganTimeNanoseconds = -1;
+ private long mQueryStartTimeNanoseconds = -1;
+ private long mUiCallStartTimeNanoseconds = -1;
+ private long mUiCallEndTimeNanoseconds = -1;
private long mFinalFinishTimeNanoseconds = -1;
private int mChosenProviderStatus = -1;
- public ChosenProviderMetric() {}
+ public ChosenProviderMetric() {
+ }
+
+ /* ------------------- UID ------------------- */
public int getChosenUid() {
return mChosenUid;
@@ -37,54 +61,143 @@ public class ChosenProviderMetric {
mChosenUid = chosenUid;
}
- public long getStartTimeNanoseconds() {
- return mStartTimeNanoseconds;
+ /* ---------------- Latencies ------------------ */
+
+
+ /* ----- Direct Latencies ------- */
+
+ /**
+ * In order for a chosen provider to be selected, the call must have successfully begun.
+ * Thus, the {@link InitialPhaseMetric} can directly pass this initial latency figure into
+ * this chosen provider metric.
+ *
+ * @param preQueryPhaseLatencyMicroseconds the millisecond latency for the service start,
+ * typically passed in through the
+ * {@link InitialPhaseMetric}
+ */
+ public void setPreQueryPhaseLatencyMicroseconds(int preQueryPhaseLatencyMicroseconds) {
+ mPreQueryPhaseLatencyMicroseconds = preQueryPhaseLatencyMicroseconds;
+ }
+
+ /**
+ * In order for a chosen provider to be selected, a candidate provider must exist. The
+ * candidate provider can directly pass the final latency figure into this chosen provider
+ * metric.
+ *
+ * @param queryPhaseLatencyMicroseconds the millisecond latency for the query phase, typically
+ * passed in through the {@link CandidateProviderMetric}
+ */
+ public void setQueryPhaseLatencyMicroseconds(int queryPhaseLatencyMicroseconds) {
+ mQueryPhaseLatencyMicroseconds = queryPhaseLatencyMicroseconds;
}
- public void setStartTimeNanoseconds(long startTimeNanoseconds) {
- mStartTimeNanoseconds = startTimeNanoseconds;
+ public int getPreQueryPhaseLatencyMicroseconds() {
+ return mPreQueryPhaseLatencyMicroseconds;
}
- public long getQueryFinishTimeNanoseconds() {
- return mQueryFinishTimeNanoseconds;
+ public int getQueryPhaseLatencyMicroseconds() {
+ return mQueryPhaseLatencyMicroseconds;
}
- public void setQueryFinishTimeNanoseconds(long queryFinishTimeNanoseconds) {
- mQueryFinishTimeNanoseconds = queryFinishTimeNanoseconds;
+ public int getUiPhaseLatencyMicroseconds() {
+ return (int) ((this.mUiCallEndTimeNanoseconds
+ - this.mUiCallStartTimeNanoseconds) / 1000);
}
- public long getFinalFinishTimeNanoseconds() {
- return mFinalFinishTimeNanoseconds;
+ /**
+ * Returns the full provider (invocation to response) latency in microseconds. Expects the
+ * start time to be provided, such as from {@link CandidateProviderMetric}.
+ */
+ public int getEntireProviderLatencyMicroseconds() {
+ return (int) ((this.mFinalFinishTimeNanoseconds
+ - this.mQueryStartTimeNanoseconds) / 1000);
+ }
+
+ /**
+ * Returns the full (platform invoked to response) latency in microseconds. Expects the
+ * start time to be provided, such as from {@link InitialPhaseMetric}.
+ */
+ public int getEntireLatencyMicroseconds() {
+ return (int) ((this.mFinalFinishTimeNanoseconds
+ - this.mServiceBeganTimeNanoseconds) / 1000);
+ }
+
+ /* ----- Timestamps for Latency ----- */
+
+ /**
+ * In order for a chosen provider to be selected, the call must have successfully begun.
+ * Thus, the {@link InitialPhaseMetric} can directly pass this initial timestamp into this
+ * chosen provider metric.
+ *
+ * @param serviceBeganTimeNanoseconds the timestamp moment when the platform was called,
+ * typically passed in through the {@link InitialPhaseMetric}
+ */
+ public void setServiceBeganTimeNanoseconds(long serviceBeganTimeNanoseconds) {
+ mServiceBeganTimeNanoseconds = serviceBeganTimeNanoseconds;
+ }
+
+ public void setQueryStartTimeNanoseconds(long queryStartTimeNanoseconds) {
+ mQueryStartTimeNanoseconds = queryStartTimeNanoseconds;
+ }
+
+ public void setUiCallStartTimeNanoseconds(long uiCallStartTimeNanoseconds) {
+ this.mUiCallStartTimeNanoseconds = uiCallStartTimeNanoseconds;
+ }
+
+ public void setUiCallEndTimeNanoseconds(long uiCallEndTimeNanoseconds) {
+ this.mUiCallEndTimeNanoseconds = uiCallEndTimeNanoseconds;
}
public void setFinalFinishTimeNanoseconds(long finalFinishTimeNanoseconds) {
mFinalFinishTimeNanoseconds = finalFinishTimeNanoseconds;
}
- public int getChosenProviderStatus() {
- return mChosenProviderStatus;
+ public long getServiceBeganTimeNanoseconds() {
+ return mServiceBeganTimeNanoseconds;
}
- public void setChosenProviderStatus(int chosenProviderStatus) {
- mChosenProviderStatus = chosenProviderStatus;
+ public long getQueryStartTimeNanoseconds() {
+ return mQueryStartTimeNanoseconds;
}
- /**
- * Returns the full provider (invocation to response) latency in microseconds.
- */
- public int getEntireProviderLatencyMs() {
- return (int) ((this.getFinalFinishTimeNanoseconds()
- - this.getStartTimeNanoseconds()) / 1000);
+ public long getUiCallStartTimeNanoseconds() {
+ return mUiCallStartTimeNanoseconds;
}
- // TODO get post click final phase and re-add the query phase time to metric
+ public long getUiCallEndTimeNanoseconds() {
+ return mUiCallEndTimeNanoseconds;
+ }
+
+ public long getFinalFinishTimeNanoseconds() {
+ return mFinalFinishTimeNanoseconds;
+ }
+
+ /* --- Time Stamp Conversion to Microseconds --- */
/**
- * Returns the end of query to response phase latency in microseconds.
+ * We collect raw timestamps in nanoseconds for ease of collection. However, given the scope
+ * of our logging timeframe, and size considerations of the metric, we require these to give us
+ * the microsecond timestamps from the start reference point.
+ *
+ * @param specificTimestamp the timestamp to consider, must be greater than the reference
+ * @return the microsecond integer timestamp from service start to query began
*/
- public int getFinalPhaseLatencyMs() {
- return (int) ((this.getFinalFinishTimeNanoseconds()
- - this.getQueryFinishTimeNanoseconds()) / 1000);
+ public int getTimestampFromReferenceStartMicroseconds(long specificTimestamp) {
+ if (specificTimestamp < this.mServiceBeganTimeNanoseconds) {
+ Log.i(TAG, "The timestamp is before service started, falling back to default int");
+ return MetricUtilities.DEFAULT_INT_32;
+ }
+ return (int) ((specificTimestamp
+ - this.mServiceBeganTimeNanoseconds) / 1000);
+ }
+
+ /* ----------- Provider Status -------------- */
+
+ public int getChosenProviderStatus() {
+ return mChosenProviderStatus;
}
+ public void setChosenProviderStatus(int chosenProviderStatus) {
+ mChosenProviderStatus = chosenProviderStatus;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
new file mode 100644
index 000000000000..5f062b05df67
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server.credentials.metrics;
+
+/**
+ * This handles metrics collected prior to any remote calls to providers.
+ * Some types are redundant across these metric collectors, but that has debug use-cases as
+ * these data-types are available at different moments of the flow (and typically, one can feed
+ * into the next).
+ * TODO(b/270403549) - iterate on this in V3+
+ */
+public class InitialPhaseMetric {
+ private static final String TAG = "PreCandidateMetric";
+
+ // The api being called, default set to unknown
+ private int mApiName = ApiName.UNKNOWN.getMetricCode();
+ // The caller uid of the calling application, default to -1
+ private int mCallerUid = -1;
+ // The session id to unite multiple atom emits, default to -1
+ private long mSessionId = -1;
+ // A sequence id to order united emits, default to -1
+ private int mSequenceId = -1;
+ private int mCountRequestClassType = -1;
+
+ // Raw timestamps in nanoseconds, *the only* one logged as such (i.e. 64 bits) since it is a
+ // reference point.
+ private long mCredentialServiceStartedTimeNanoseconds = -1;
+
+ // A reference point to give this object utility to capture latency. Can be directly handed
+ // over to the next latency object.
+ private long mCredentialServiceBeginQueryTimeNanoseconds = -1;
+
+
+ public InitialPhaseMetric() {
+ }
+
+ /* ---------- Latencies ---------- */
+
+ /* -- Direct Latencies -- */
+
+ public int getServiceStartToQueryLatencyMicroseconds() {
+ return (int) ((this.mCredentialServiceStartedTimeNanoseconds
+ - this.mCredentialServiceBeginQueryTimeNanoseconds) / 1000);
+ }
+
+ /* -- Timestamps -- */
+
+ public void setCredentialServiceStartedTimeNanoseconds(
+ long credentialServiceStartedTimeNanoseconds
+ ) {
+ this.mCredentialServiceStartedTimeNanoseconds = credentialServiceStartedTimeNanoseconds;
+ }
+
+ public void setCredentialServiceBeginQueryTimeNanoseconds(
+ long credentialServiceBeginQueryTimeNanoseconds) {
+ mCredentialServiceBeginQueryTimeNanoseconds = credentialServiceBeginQueryTimeNanoseconds;
+ }
+
+ public long getCredentialServiceStartedTimeNanoseconds() {
+ return mCredentialServiceStartedTimeNanoseconds;
+ }
+
+ public long getCredentialServiceBeginQueryTimeNanoseconds() {
+ return mCredentialServiceBeginQueryTimeNanoseconds;
+ }
+
+ /* ------ ApiName ------ */
+
+ public void setApiName(int apiName) {
+ mApiName = apiName;
+ }
+
+ public int getApiName() {
+ return mApiName;
+ }
+
+ /* ------ CallerUid ------ */
+
+ public void setCallerUid(int callerUid) {
+ mCallerUid = callerUid;
+ }
+
+ public int getCallerUid() {
+ return mCallerUid;
+ }
+
+ /* ------ SessionId ------ */
+
+ public void setSessionId(long sessionId) {
+ mSessionId = sessionId;
+ }
+
+ public long getSessionId() {
+ return mSessionId;
+ }
+
+ /* ------ SequenceId ------ */
+
+ public void setSequenceId(int sequenceId) {
+ mSequenceId = sequenceId;
+ }
+
+ public int getSequenceId() {
+ return mSequenceId;
+ }
+
+ /* ------ Count Request Class Types ------ */
+
+ public void setCountRequestClassType(int countRequestClassType) {
+ mCountRequestClassType = countRequestClassType;
+ }
+
+ public int getCountRequestClassType() {
+ return mCountRequestClassType;
+ }
+}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
new file mode 100644
index 000000000000..08f1afa2f438
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/ProviderStatusForMetrics.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.credentials.metrics;
+
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN;
+
+public enum ProviderStatusForMetrics {
+
+ UNKNOWN(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_UNKNOWN),
+ FINAL_FAILURE(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_FAILURE),
+ QUERY_FAILURE(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_FAILURE),
+ FINAL_SUCCESS(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_FINAL_SUCCESS),
+ QUERY_SUCCESS(
+ CREDENTIAL_MANAGER_API_CALLED__CANDIDATE_PROVIDER_STATUS__PROVIDER_QUERY_SUCCESS);
+
+ private final int mInnerMetricCode;
+
+ ProviderStatusForMetrics(int innerMetricCode) {
+ this.mInnerMetricCode = innerMetricCode;
+ }
+
+ /**
+ * Gives the West-world version of the metric name.
+ *
+ * @return a code corresponding to the west world metric name
+ */
+ public int getMetricCode() {
+ return this.mInnerMetricCode;
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
index 4351bc1c81ce..80100a927e82 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyCacheImpl.java
@@ -24,6 +24,8 @@ import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -52,6 +54,11 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache {
@GuardedBy("mLock")
private final SparseIntArray mPermissionPolicy = new SparseIntArray();
+ @GuardedBy("mLock")
+ private List<String> mLauncherShortcutOverrides =
+ new ArrayList<>();
+
+
/** Maps to {@code ActiveAdmin.mAdminCanGrantSensorsPermissions}. */
private final AtomicBoolean mCanGrantSensorsPermissions = new AtomicBoolean(false);
@@ -122,6 +129,22 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache {
mCanGrantSensorsPermissions.set(canGrant);
}
+ @Override
+ public List<String> getLauncherShortcutOverrides() {
+ synchronized (mLock) {
+ return new ArrayList<>(mLauncherShortcutOverrides);
+ }
+ }
+
+ /**
+ * Sets a list of packages for which shortcuts should be replaced by their badged version.
+ */
+ public void setLauncherShortcutOverrides(List<String> launcherShortcutOverrides) {
+ synchronized (mLock) {
+ mLauncherShortcutOverrides = new ArrayList<>(launcherShortcutOverrides);
+ }
+ }
+
/** Dump content */
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
@@ -131,6 +154,8 @@ public class DevicePolicyCacheImpl extends DevicePolicyCache {
pw.println("Password quality: " + mPasswordQuality);
pw.println("Permission policy: " + mPermissionPolicy);
pw.println("Admin can grant sensors permission: " + mCanGrantSensorsPermissions.get());
+ pw.print("Shortcuts overrides: ");
+ pw.println(mLauncherShortcutOverrides);
pw.decreaseIndent();
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e9c23a052422..a4e563b21bec 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3212,8 +3212,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void sendChangedNotification(int userHandle) {
Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ Bundle options = new BroadcastOptions()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferUntilActive(true)
+ .toBundle();
mInjector.binderWithCleanCallingIdentity(() ->
- mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle)));
+ mContext.sendBroadcastAsUser(intent, new UserHandle(userHandle), null, options));
}
private void loadSettingsLocked(DevicePolicyData policy, int userHandle) {
@@ -3518,16 +3522,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
userId == UserHandle.USER_SYSTEM ? UserHandle.USER_ALL : userId);
updatePermissionPolicyCache(userId);
updateAdminCanGrantSensorsPermissionCache(userId);
-
final List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs;
+ boolean isManagedSubscription;
+
synchronized (getLockObject()) {
ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userId);
preferentialNetworkServiceConfigs = owner != null
? owner.mPreferentialNetworkServiceConfigs
: List.of(PreferentialNetworkServiceConfig.DEFAULT);
+
+ isManagedSubscription = owner != null && owner.mManagedSubscriptionsPolicy != null
+ && owner.mManagedSubscriptionsPolicy.getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS;
}
updateNetworkPreferenceForUser(userId, preferentialNetworkServiceConfigs);
+ if (isManagedSubscription) {
+ String defaultDialerPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultDialer);
+ String defaultSmsPackageName = getDefaultRoleHolderPackageName(
+ com.android.internal.R.string.config_defaultSms);
+ updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
+ defaultSmsPackageName);
+ }
+
startOwnerService(userId, "start-user");
if (isDevicePolicyEngineEnabled()) {
mDevicePolicyEngine.handleStartUser(userId);
@@ -7610,6 +7628,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (isWorkProfileTelephonyFlagEnabled()) {
clearManagedSubscriptionsPolicy();
+ clearLauncherShortcutOverrides();
updateTelephonyCrossProfileIntentFilters(parentId, UserHandle.USER_NULL, false);
}
Slogf.i(LOG_TAG, "Cleaning up device-wide policies done.");
@@ -7627,6 +7646,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private void clearLauncherShortcutOverrides() {
+ mPolicyCache.setLauncherShortcutOverrides(new ArrayList<>());
+ }
+
private void updateTelephonyCrossProfileIntentFilters(int parentUserId, int profileUserId,
boolean enableWorkTelephony) {
try {
@@ -22751,12 +22774,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else {
Slogf.w(LOG_TAG, "Couldn't install sms app, sms app package is null");
}
+
+ updateDialerAndSmsManagedShortcutsOverrideCache(defaultDialerPackageName,
+ defaultSmsPackageName);
} catch (RemoteException re) {
// shouldn't happen
Slogf.wtf(LOG_TAG, "Failed to install dialer/sms app", re);
}
}
+ private void updateDialerAndSmsManagedShortcutsOverrideCache(
+ String defaultDialerPackageName, String defaultSmsPackageName) {
+
+ List<String> shortcutOverrides = new ArrayList<>();
+
+ if (defaultDialerPackageName != null) {
+ shortcutOverrides.add(defaultDialerPackageName);
+ }
+
+ if (defaultSmsPackageName != null) {
+ shortcutOverrides.add(defaultSmsPackageName);
+ }
+ mPolicyCache.setLauncherShortcutOverrides(shortcutOverrides);
+ }
+
private void registerListenerToAssignSubscriptionsToUser(int userId) {
synchronized (mSubscriptionsChangedListenerLock) {
if (mSubscriptionsChangedListener != null) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3ca158dc9c96..194647fda92c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -120,7 +120,9 @@ class Owners {
} else {
mDeviceStateCache.setDeviceOwnerType(NO_DEVICE_OWNER);
}
-
+ for (int userId : usersIds) {
+ mDeviceStateCache.setHasProfileOwner(userId, hasProfileOwner(userId));
+ }
} else {
mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
for (int userId : usersIds) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 850b5b686efe..edfe95efe7f9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2739,6 +2739,14 @@ public final class SystemServer implements Dumpable {
}
t.traceEnd();
+ t.traceBegin("RegisterLogMteState");
+ try {
+ LogMteState.register(context);
+ } catch (Throwable e) {
+ reportWtf("RegisterLogMteState", e);
+ }
+ t.traceEnd();
+
// Emit any pending system_server WTFs
synchronized (SystemService.class) {
if (sPendingWtfs != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 99da415380cd..8a5d3a6772a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -558,7 +558,7 @@ public final class BroadcastQueueModernImplTest {
// To maximize test coverage, dump current state; we're not worried
// about the actual output, just that we don't crash
- queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED);
+ queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED, "Test-driven");
queue.dumpLocked(SystemClock.uptimeMillis(),
new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream())));
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index cd5ac7bcb1e5..1731590be3c9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -782,6 +782,34 @@ public class AppOpsUidStateTrackerTest {
assertTrue(mIntf.isUidInForeground(UID));
}
+ @Test
+ public void testAppWidgetVisibleDoesntChangeUidState() {
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>();
+ updatedAppWidgetVisibilities.put(UID, "");
+
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true);
+
+ assertEquals(UID_STATE_TOP, mIntf.getUidState(UID));
+ }
+
+ @Test
+ public void testAppWidgetNotVisibleDoesntChangeUidState() {
+ SparseArray<String> updatedAppWidgetVisibilities = new SparseArray<>();
+ updatedAppWidgetVisibilities.put(UID, "");
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, true);
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ mIntf.updateAppWidgetVisibility(updatedAppWidgetVisibilities, false);
+
+ assertEquals(UID_STATE_TOP, mIntf.getUidState(UID));
+ }
+
public void testUidStateChangedCallback(int initialState, int finalState) {
int initialUidState = processStateToUidState(initialState);
int finalUidState = processStateToUidState(finalState);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 71492656913b..6861c2f049fb 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -108,6 +108,7 @@
<uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
+ <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml
index 498b676dd1dc..1d2a7d37d613 100644
--- a/services/tests/servicestests/res/xml/irq_device_map_3.xml
+++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml
@@ -21,6 +21,6 @@
<subsystem>Alarm</subsystem>
</device>
<device name="test.wifi.device">
- <subsystem>undefined</subsystem>
+ <subsystem>Wifi</subsystem>
</device>
</irq-device-map> \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 3ff802c0125c..6b0e33037af8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,11 +32,15 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
+import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.WakeReason;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
import android.os.IBinder;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -69,6 +75,8 @@ public class FaceAuthenticationClientTest {
private static final int USER_ID = 12;
private static final long OP_ID = 32;
+ private static final int WAKE_REASON = WakeReason.LIFT;
+ private static final int AUTH_REASON = AuthenticateReason.Face.ASSISTANT_VISIBLE;
@Rule
public final TestableContext mContext = new TestableContext(
@@ -126,8 +134,13 @@ public class FaceAuthenticationClientTest {
InOrder order = inOrder(mHal, mBiometricContext);
order.verify(mBiometricContext).updateContext(
mOperationContextCaptor.capture(), anyBoolean());
- order.verify(mHal).authenticateWithContext(
- eq(OP_ID), same(mOperationContextCaptor.getValue().toAidlContext()));
+
+ final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+ order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
+ assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
+ assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
+ .isEqualTo(AUTH_REASON);
+
verify(mHal, never()).authenticate(anyLong());
}
@@ -156,8 +169,11 @@ public class FaceAuthenticationClientTest {
final AidlSession aidl = new AidlSession(version, mHal, USER_ID, mHalSessionCallback);
final FaceAuthenticateOptions options = new FaceAuthenticateOptions.Builder()
.setOpPackageName("test-owner")
- .setUserId(5)
+ .setUserId(USER_ID)
.setSensorId(9)
+ .setWakeReason(PowerManager.WAKE_REASON_LIFT)
+ .setAuthenticateReason(
+ FaceAuthenticateOptions.AUTHENTICATE_REASON_ASSISTANT_VISIBLE)
.build();
return new FaceAuthenticationClient(mContext, () -> aidl, mToken,
2 /* requestId */, mClientMonitorCallbackConverter, OP_ID,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
index c4c550549f73..0abfa7e6546d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceDetectClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.inOrder;
@@ -24,9 +26,13 @@ import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
+import android.hardware.biometrics.common.WakeReason;
import android.hardware.biometrics.face.ISession;
import android.hardware.face.FaceAuthenticateOptions;
import android.os.IBinder;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -55,6 +61,8 @@ import org.mockito.junit.MockitoRule;
public class FaceDetectClientTest {
private static final int USER_ID = 12;
+ private static final int WAKE_REASON = WakeReason.POWER_BUTTON;
+ private static final int AUTH_REASON = AuthenticateReason.Face.OCCLUDING_APP_REQUESTED;
@Rule
public final TestableContext mContext = new TestableContext(
@@ -103,8 +111,13 @@ public class FaceDetectClientTest {
InOrder order = inOrder(mHal, mBiometricContext);
order.verify(mBiometricContext).updateContext(
mOperationContextCaptor.capture(), anyBoolean());
- order.verify(mHal).detectInteractionWithContext(
- same(mOperationContextCaptor.getValue().toAidlContext()));
+
+ final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+ order.verify(mHal).detectInteractionWithContext(same(aidlContext));
+ assertThat(aidlContext.wakeReason).isEqualTo(WAKE_REASON);
+ assertThat(aidlContext.authenticateReason.getFaceAuthenticateReason())
+ .isEqualTo(AUTH_REASON);
+
verify(mHal, never()).detectInteraction();
}
@@ -118,6 +131,9 @@ public class FaceDetectClientTest {
.setUserId(USER_ID)
.setSensorId(5)
.setOpPackageName("own-it")
+ .setWakeReason(PowerManager.WAKE_REASON_POWER_BUTTON)
+ .setAuthenticateReason(
+ FaceAuthenticateOptions.AUTHENTICATE_REASON_OCCLUDING_APP_REQUESTED)
.build(),
mBiometricLogger, mBiometricContext,
false /* isStrongBiometric */, null /* sensorPrivacyManager */);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index f0f975ccf5ff..c6645003c7e2 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -36,6 +36,7 @@ import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentName;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.common.AuthenticateReason;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
@@ -165,8 +166,12 @@ public class FingerprintAuthenticationClientTest {
InOrder order = inOrder(mHal, mBiometricContext);
order.verify(mBiometricContext).updateContext(
mOperationContextCaptor.capture(), anyBoolean());
- order.verify(mHal).authenticateWithContext(
- eq(OP_ID), same(mOperationContextCaptor.getValue().toAidlContext()));
+
+ final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+ order.verify(mHal).authenticateWithContext(eq(OP_ID), same(aidlContext));
+ assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
+ .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
+
verify(mHal, never()).authenticate(anyLong());
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index e741e446da85..c20cc392f5c0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.inOrder;
@@ -24,6 +26,8 @@ import static org.mockito.Mockito.same;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.hardware.biometrics.common.AuthenticateReason;
+import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.fingerprint.FingerprintAuthenticateOptions;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -108,8 +112,12 @@ public class FingerprintDetectClientTest {
InOrder order = inOrder(mHal, mBiometricContext);
order.verify(mBiometricContext).updateContext(
mOperationContextCaptor.capture(), anyBoolean());
- order.verify(mHal).detectInteractionWithContext(
- same(mOperationContextCaptor.getValue().toAidlContext()));
+
+ final OperationContext aidlContext = mOperationContextCaptor.getValue().toAidlContext();
+ order.verify(mHal).detectInteractionWithContext(same(aidlContext));
+ assertThat(aidlContext.authenticateReason.getFingerprintAuthenticateReason())
+ .isEqualTo(AuthenticateReason.Fingerprint.UNKNOWN);
+
verify(mHal, never()).detectInteraction();
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 2967c5c53e8a..339ccd80c351 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -130,7 +130,6 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -283,7 +282,7 @@ public class VirtualDeviceManagerServiceTest {
return blockedActivities;
}
- private Intent createRestrictedActivityBlockedIntent(List displayCategories,
+ private Intent createRestrictedActivityBlockedIntent(Set<String> displayCategories,
String targetDisplayCategory) {
when(mDisplayManagerInternalMock.createVirtualDisplay(any(), any(), any(), any(),
eq(NONBLOCKED_APP_PACKAGE_NAME))).thenReturn(DISPLAY_ID_1);
@@ -1634,7 +1633,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void nonRestrictedActivityOnRestrictedVirtualDisplay_startBlockedAlertActivity() {
- Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"),
+ Intent blockedAppIntent = createRestrictedActivityBlockedIntent(Set.of("abc"),
/* targetDisplayCategory= */ null);
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
@@ -1642,7 +1641,7 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void restrictedActivityOnRestrictedVirtualDisplay_doesNotStartBlockedAlertActivity() {
- Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "abc");
+ Intent blockedAppIntent = createRestrictedActivityBlockedIntent(Set.of("abc"), "abc");
verify(mContext, never()).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@@ -1650,14 +1649,14 @@ public class VirtualDeviceManagerServiceTest {
@Test
public void restrictedActivityOnNonRestrictedVirtualDisplay_startBlockedAlertActivity() {
Intent blockedAppIntent = createRestrictedActivityBlockedIntent(
- /* displayCategories= */ List.of(), "abc");
+ /* displayCategories= */ Set.of(), "abc");
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
@Test
public void restrictedActivityNonMatchingRestrictedVirtualDisplay_startBlockedAlertActivity() {
- Intent blockedAppIntent = createRestrictedActivityBlockedIntent(List.of("abc"), "def");
+ Intent blockedAppIntent = createRestrictedActivityBlockedIntent(Set.of("abc"), "def");
verify(mContext).startActivityAsUser(argThat(intent ->
intent.filterEquals(blockedAppIntent)), any(), any());
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index d9e4da73c1c3..2bfa44ecb1d6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -89,7 +89,7 @@ public class VirtualAudioControllerTest {
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ null,
- /* displayCategories= */ new ArrayList<>(),
+ /* displayCategories= */ new ArraySet<>(),
/* showTasksInHostDeviceRecents= */ true);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 1f25da7a3cef..aaabb286589c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -162,9 +162,9 @@ import com.android.server.pm.UserRestrictionsUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
-import org.junit.Ignore;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
@@ -514,7 +514,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(mContext.spiedContext).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- MockUtils.checkUserHandle(CALLER_USER_HANDLE));
+ MockUtils.checkUserHandle(CALLER_USER_HANDLE),
+ eq(null),
+ any(Bundle.class));
verify(mContext.spiedContext).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_ENABLED),
@@ -793,7 +795,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- MockUtils.checkUserHandle(CALLER_USER_HANDLE));
+ MockUtils.checkUserHandle(CALLER_USER_HANDLE),
+ eq(null),
+ any(Bundle.class));
// Remove. No permissions, but same user, so it'll work.
mContext.callerPermissions.clear();
@@ -820,7 +824,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- MockUtils.checkUserHandle(CALLER_USER_HANDLE));
+ MockUtils.checkUserHandle(CALLER_USER_HANDLE),
+ eq(null),
+ any(Bundle.class));
// TODO Check other internal calls.
}
@@ -846,7 +852,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- MockUtils.checkUserHandle(CALLER_USER_HANDLE));
+ MockUtils.checkUserHandle(CALLER_USER_HANDLE),
+ eq(null),
+ any(Bundle.class));
// Remove. No permissions, but same user, so it'll work.
mContext.callerPermissions.clear();
@@ -874,7 +882,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(mContext.spiedContext, times(3)).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- MockUtils.checkUserHandle(CALLER_USER_HANDLE));
+ MockUtils.checkUserHandle(CALLER_USER_HANDLE),
+ eq(null),
+ any(Bundle.class));
}
/**
@@ -2425,7 +2435,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- MockUtils.checkUserHandle(CALLER_USER_HANDLE));
+ MockUtils.checkUserHandle(CALLER_USER_HANDLE),
+ eq(null),
+ any(Bundle.class));
verify(mContext.spiedContext).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED),
@@ -5886,7 +5898,9 @@ public class DevicePolicyManagerTest extends DpmTestBase {
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
MockUtils.checkIntentAction(
DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
- MockUtils.checkUserHandle(userHandle));
+ MockUtils.checkUserHandle(userHandle),
+ eq(null),
+ any(Bundle.class));
final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
intent.setComponent(admin1);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index 85a2446cc316..375b52d2d5e4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -21,6 +21,7 @@ import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
import static android.app.admin.SystemUpdatePolicy.TYPE_INSTALL_WINDOWED;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.content.ComponentName;
import android.os.IpcDataCache;
@@ -43,6 +44,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class OwnersTest extends DpmTestBase {
+ private static final int TEST_PO_USER = 10;
private static final String TESTDPC_PACKAGE = "com.afwsamples.testdpc";
private final DeviceStateCacheImpl mDeviceStateCache = new DeviceStateCacheImpl();
@@ -55,11 +57,11 @@ public class OwnersTest extends DpmTestBase {
@Test
public void loadProfileOwner() throws Exception {
- getServices().addUsers(10);
+ getServices().addUsers(TEST_PO_USER);
final Owners owners = makeOwners();
- DpmTestUtils.writeToFile(owners.getProfileOwnerFile(10),
+ DpmTestUtils.writeToFile(owners.getProfileOwnerFile(TEST_PO_USER),
DpmTestUtils.readAsset(mRealTestContext, "OwnersTest/profile_owner_1.xml"));
owners.load();
@@ -71,6 +73,9 @@ public class OwnersTest extends DpmTestBase {
assertThat(owners.getProfileOwnerComponent(10))
.isEqualTo(new ComponentName(TESTDPC_PACKAGE,
"com.afwsamples.testdpc.DeviceAdminReceiver"));
+
+ assertWithMessage("Profile owner data in DeviceStateCache wasn't populated")
+ .that(mDeviceStateCache.isUserOrganizationManaged(TEST_PO_USER)).isTrue();
}
@Test
@@ -90,6 +95,10 @@ public class OwnersTest extends DpmTestBase {
"com.afwsamples.testdpc.DeviceAdminReceiver"));
assertThat(owners.getSystemUpdatePolicy().getPolicyType()).isEqualTo(TYPE_INSTALL_WINDOWED);
+
+ assertWithMessage("Device owner data in DeviceStateCache wasn't populated")
+ .that(mDeviceStateCache.isUserOrganizationManaged(owners.getDeviceOwnerUserId()))
+ .isTrue();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 7971fd71ea09..94d30bb4440b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -24,6 +24,8 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_D
import static com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -32,8 +34,10 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -61,10 +65,13 @@ import android.hardware.display.HdrConversionMode;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
+import android.media.projection.IMediaProjectionManager;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.MessageQueue;
import android.os.Process;
+import android.view.ContentRecordingSession;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
@@ -158,25 +165,40 @@ public class DisplayManagerServiceTest {
}
};
- class BasicInjector extends DisplayManagerService.Injector {
- @Override
- VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
- return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
- (String name, boolean secure, float refreshRate) -> mMockDisplayToken);
- }
-
- @Override
- LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
- Handler handler, DisplayAdapter.Listener displayAdapterListener) {
- return new LocalDisplayAdapter(syncRoot, context, handler,
- displayAdapterListener, new LocalDisplayAdapter.Injector() {
- @Override
- public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
- return mSurfaceControlProxy;
- }
- });
- }
+ class BasicInjector extends DisplayManagerService.Injector {
+ @Override
+ IMediaProjectionManager getProjectionService() {
+ return mMockProjectionService;
+ }
+
+ @Override
+ VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+ new VirtualDisplayAdapter.SurfaceControlDisplayFactory() {
+ @Override
+ public IBinder createDisplay(String name, boolean secure,
+ float requestedRefreshRate) {
+ return mMockDisplayToken;
+ }
+
+ @Override
+ public void destroyDisplay(IBinder displayToken) {
+ }
+ });
+ }
+
+ @Override
+ LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
+ Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+ return new LocalDisplayAdapter(syncRoot, context, handler,
+ displayAdapterListener, new LocalDisplayAdapter.Injector() {
+ @Override
+ public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
+ return mSurfaceControlProxy;
+ }
+ });
+ }
@Override
int setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
@@ -198,6 +220,7 @@ public class DisplayManagerServiceTest {
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
+ @Mock IMediaProjectionManager mMockProjectionService;
@Mock IVirtualDeviceManager mIVirtualDeviceManager;
@Mock InputManagerInternal mMockInputManagerInternal;
@Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
@@ -285,6 +308,7 @@ public class DisplayManagerServiceTest {
builder.setFlags(flags);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -410,6 +434,7 @@ public class DisplayManagerServiceTest {
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -446,6 +471,7 @@ public class DisplayManagerServiceTest {
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -479,6 +505,7 @@ public class DisplayManagerServiceTest {
builder.setUniqueId(uniqueId);
int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
/* projection= */ null, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -720,6 +747,7 @@ public class DisplayManagerServiceTest {
builder.setUniqueId(uniqueId);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -731,6 +759,7 @@ public class DisplayManagerServiceTest {
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -768,6 +797,7 @@ public class DisplayManagerServiceTest {
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. This should be added to the previously created display
@@ -783,6 +813,7 @@ public class DisplayManagerServiceTest {
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertEquals(
@@ -820,6 +851,7 @@ public class DisplayManagerServiceTest {
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId1 = localService.getDisplayInfo(displayId1).displayGroupId;
// Create a second virtual display. With the flag VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
@@ -838,6 +870,7 @@ public class DisplayManagerServiceTest {
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
int displayGroupId2 = localService.getDisplayInfo(displayId2).displayGroupId;
assertNotEquals(
@@ -881,6 +914,7 @@ public class DisplayManagerServiceTest {
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -906,6 +940,7 @@ public class DisplayManagerServiceTest {
virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is set.
assertNotEquals(
@@ -929,6 +964,7 @@ public class DisplayManagerServiceTest {
null /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class),
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// Check that FLAG_ALWAYS_UNLOCKED is not set.
assertEquals(
@@ -960,6 +996,7 @@ public class DisplayManagerServiceTest {
.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
final int firstDisplayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
// The second virtual display requests to mirror the first virtual display.
final String uniqueId2 = "uniqueId --- displayIdToMirrorTest #2";
@@ -967,10 +1004,11 @@ public class DisplayManagerServiceTest {
final VirtualDisplayConfig.Builder builder2 = new VirtualDisplayConfig.Builder(
VIRTUAL_DISPLAY_NAME, width, height, dpi)
.setUniqueId(uniqueId2)
- .setWindowManagerMirroring(true);
+ .setWindowManagerMirroringEnabled(true);
final int secondDisplayId = binderService.createVirtualDisplay(builder2.build(),
mMockAppToken2 /* callback */, null /* projection */,
PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
// flush the handler
@@ -985,6 +1023,54 @@ public class DisplayManagerServiceTest {
Display.INVALID_DISPLAY);
}
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() throws Exception {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mMockWindowManagerInternal
+ .setContentRecordingSession(any(ContentRecordingSession.class)))
+ .thenReturn(true);
+
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession true");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws Exception {
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ when(mMockWindowManagerInternal
+ .setContentRecordingSession(any(ContentRecordingSession.class)))
+ .thenReturn(false);
+
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, 600, 800, 320);
+ builder.setUniqueId("uniqueId --- setContentRecordingSession false");
+ builder.setContentRecordingSession(
+ ContentRecordingSession.createDisplaySession(new Binder("")));
+
+ DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+ displayManager.windowManagerAndInputReady();
+
+ DisplayManagerService.BinderService binderService = displayManager.new BinderService();
+ final int displayId = binderService.createVirtualDisplay(builder.build(),
+ mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+
+ assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY);
+ }
+
/**
* Tests that the virtual display is created with
* {@link VirtualDisplayConfig.Builder#setSurface(Surface)}
@@ -1011,6 +1097,7 @@ public class DisplayManagerServiceTest {
builder.setUniqueId(uniqueId);
final int displayId = binderService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -1043,6 +1130,7 @@ public class DisplayManagerServiceTest {
int displayId = bs.createVirtualDisplay(builder.build(), mMockAppToken /* callback */,
null /* projection */, PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
@@ -1093,7 +1181,6 @@ public class DisplayManagerServiceTest {
registerDefaultDisplays(displayManager);
- DisplayManagerService.BinderService bs = displayManager.new BinderService();
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
@@ -1111,6 +1198,7 @@ public class DisplayManagerServiceTest {
int displayId = localService.createVirtualDisplay(builder.build(),
mMockAppToken /* callback */, virtualDevice /* virtualDeviceToken */,
mock(DisplayWindowPolicyController.class), PACKAGE_NAME);
+ verify(mMockWindowManagerInternal, never()).setContentRecordingSession(Mockito.any());
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
displayManager.getDisplayHandler().runWithScissors(() -> {}, 0 /* now */);
DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index b2bfd2bf720c..b660926f1394 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -256,12 +256,16 @@ class KeyboardLayoutManagerTests {
@Test
fun testNewUi_getKeyboardLayoutsForInputDevice() {
NewSettingsApiFlag(true).use {
- val keyboardLayouts =
- keyboardLayoutManager.getKeyboardLayoutsForInputDevice(keyboardDevice.identifier)
- assertEquals(
- "New UI: getKeyboardLayoutsForInputDevice API should always return empty array",
- 0,
- keyboardLayouts.size
+ val keyboardLayouts = keyboardLayoutManager.keyboardLayouts
+ assertNotEquals(
+ "New UI: getKeyboardLayoutsForInputDevice API should not return empty array",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue(
+ "New UI: getKeyboardLayoutsForInputDevice API should provide English(US) " +
+ "layout",
+ hasLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
)
}
}
diff --git a/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
new file mode 100644
index 000000000000..1e73a45a0c22
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/AudioPoliciesDeviceRouteControllerTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.server.media;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.AudioRoutesInfo;
+import android.media.IAudioRoutesObserver;
+import android.media.MediaRoute2Info;
+import android.os.RemoteException;
+
+import com.android.internal.R;
+import com.android.server.audio.AudioService;
+
+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.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AudioPoliciesDeviceRouteControllerTest {
+
+ private static final String ROUTE_NAME_DEFAULT = "default";
+ private static final String ROUTE_NAME_DOCK = "dock";
+ private static final String ROUTE_NAME_HEADPHONES = "headphones";
+
+ private static final int VOLUME_SAMPLE_1 = 25;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private AudioManager mAudioManager;
+ @Mock
+ private AudioService mAudioService;
+ @Mock
+ private DeviceRouteController.OnDeviceRouteChangedListener mOnDeviceRouteChangedListener;
+
+ @Captor
+ private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
+
+ private AudioPoliciesDeviceRouteController mController;
+
+ private IAudioRoutesObserver.Stub mAudioRoutesObserver;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getText(anyInt())).thenReturn(ROUTE_NAME_DEFAULT);
+
+ // Setting built-in speaker as default speaker.
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_SPEAKER;
+ when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
+ .thenReturn(audioRoutesInfo);
+
+ mController = new AudioPoliciesDeviceRouteController(
+ mContext, mAudioManager, mAudioService, mOnDeviceRouteChangedListener);
+
+ mAudioRoutesObserver = mAudioRoutesObserverCaptor.getValue();
+ }
+
+ @Test
+ public void getDeviceRoute_noSelectedRoutes_returnsDefaultDevice() {
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+
+ assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DEFAULT);
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Test
+ public void getDeviceRoute_audioRouteHasChanged_returnsRouteFromAudioService() {
+ when(mResources.getText(R.string.default_audio_route_name_headphones))
+ .thenReturn(ROUTE_NAME_HEADPHONES);
+
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+ callAudioRoutesObserver(audioRoutesInfo);
+
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+ assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
+ }
+
+ @Test
+ public void getDeviceRoute_selectDevice_returnsSelectedRoute() {
+ when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
+ .thenReturn(ROUTE_NAME_DOCK);
+
+ mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
+
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+ assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
+ }
+
+ @Test
+ public void getDeviceRoute_hasSelectedAndAudioServiceRoutes_returnsSelectedRoute() {
+ when(mResources.getText(R.string.default_audio_route_name_headphones))
+ .thenReturn(ROUTE_NAME_HEADPHONES);
+ when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
+ .thenReturn(ROUTE_NAME_DOCK);
+
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+ callAudioRoutesObserver(audioRoutesInfo);
+
+ mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
+
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+ assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_DOCK);
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
+ }
+
+ @Test
+ public void getDeviceRoute_unselectRoute_returnsAudioServiceRoute() {
+ when(mResources.getText(R.string.default_audio_route_name_headphones))
+ .thenReturn(ROUTE_NAME_HEADPHONES);
+ when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
+ .thenReturn(ROUTE_NAME_DOCK);
+
+ mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
+
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+ callAudioRoutesObserver(audioRoutesInfo);
+
+ mController.selectRoute(null);
+
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+ assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
+ }
+
+ @Test
+ public void getDeviceRoute_selectRouteFails_returnsAudioServiceRoute() {
+ when(mResources.getText(R.string.default_audio_route_name_headphones))
+ .thenReturn(ROUTE_NAME_HEADPHONES);
+
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+ callAudioRoutesObserver(audioRoutesInfo);
+
+ mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+ assertThat(route2Info.getName()).isEqualTo(ROUTE_NAME_HEADPHONES);
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
+ }
+
+ @Test
+ public void selectRoute_selectWiredRoute_returnsTrue() {
+ assertThat(mController.selectRoute(MediaRoute2Info.TYPE_HDMI)).isTrue();
+ }
+
+ @Test
+ public void selectRoute_selectBluetoothRoute_returnsFalse() {
+ assertThat(mController.selectRoute(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)).isFalse();
+ }
+
+ @Test
+ public void selectRoute_unselectRoute_returnsTrue() {
+ assertThat(mController.selectRoute(null)).isTrue();
+ }
+
+ @Test
+ public void updateVolume_noSelectedRoute_deviceRouteVolumeChanged() {
+ when(mResources.getText(R.string.default_audio_route_name_headphones))
+ .thenReturn(ROUTE_NAME_HEADPHONES);
+
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+ callAudioRoutesObserver(audioRoutesInfo);
+
+ mController.updateVolume(VOLUME_SAMPLE_1);
+
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_WIRED_HEADPHONES);
+ assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
+ }
+
+ @Test
+ public void updateVolume_connectSelectedRouteLater_selectedRouteVolumeChanged() {
+ when(mResources.getText(R.string.default_audio_route_name_headphones))
+ .thenReturn(ROUTE_NAME_HEADPHONES);
+ when(mResources.getText(R.string.default_audio_route_name_dock_speakers))
+ .thenReturn(ROUTE_NAME_DOCK);
+
+ AudioRoutesInfo audioRoutesInfo = new AudioRoutesInfo();
+ audioRoutesInfo.mainType = AudioRoutesInfo.MAIN_HEADPHONES;
+ callAudioRoutesObserver(audioRoutesInfo);
+
+ mController.updateVolume(VOLUME_SAMPLE_1);
+
+ mController.selectRoute(MediaRoute2Info.TYPE_DOCK);
+
+ MediaRoute2Info route2Info = mController.getDeviceRoute();
+ assertThat(route2Info.getType()).isEqualTo(MediaRoute2Info.TYPE_DOCK);
+ assertThat(route2Info.getVolume()).isEqualTo(VOLUME_SAMPLE_1);
+ }
+
+ /**
+ * Simulates {@link IAudioRoutesObserver.Stub#dispatchAudioRoutesChanged(AudioRoutesInfo)}
+ * from {@link AudioService}. This happens when there is a wired route change,
+ * like a wired headset being connected.
+ *
+ * @param audioRoutesInfo updated state of connected wired device
+ */
+ private void callAudioRoutesObserver(AudioRoutesInfo audioRoutesInfo) {
+ try {
+ // this is a captured observer implementation
+ // from WiredRoutesController's AudioService#startWatchingRoutes call
+ mAudioRoutesObserver.dispatchAudioRoutesChanged(audioRoutesInfo);
+ } catch (RemoteException exception) {
+ // Should not happen since the object is mocked.
+ assertWithMessage("An unexpected RemoteException happened.").fail();
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
index 24ed42cab63a..24e48517f280 100644
--- a/services/tests/servicestests/src/com/android/server/media/DeviceRouteControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/LegacyDeviceRouteControllerTest.java
@@ -50,7 +50,7 @@ import java.util.Arrays;
import java.util.Collection;
@RunWith(Enclosed.class)
-public class DeviceRouteControllerTest {
+public class LegacyDeviceRouteControllerTest {
private static final String DEFAULT_ROUTE_NAME = "default_route";
private static final String DEFAULT_HEADPHONES_NAME = "headphone";
@@ -97,7 +97,7 @@ public class DeviceRouteControllerTest {
// Default route should be initialized even when AudioService returns null.
when(mAudioService.startWatchingRoutes(any())).thenReturn(null);
- DeviceRouteController deviceRouteController = new DeviceRouteController(
+ LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController(
mContext,
mAudioManager,
mAudioService,
@@ -122,7 +122,7 @@ public class DeviceRouteControllerTest {
AudioRoutesInfo fakeBluetoothAudioRoute = createFakeBluetoothAudioRoute();
when(mAudioService.startWatchingRoutes(any())).thenReturn(fakeBluetoothAudioRoute);
- DeviceRouteController deviceRouteController = new DeviceRouteController(
+ LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController(
mContext,
mAudioManager,
mAudioService,
@@ -236,7 +236,7 @@ public class DeviceRouteControllerTest {
when(mResources.getText(mExpectedRouteNameResource))
.thenReturn(mExpectedRouteNameValue);
- DeviceRouteController deviceRouteController = new DeviceRouteController(
+ LegacyDeviceRouteController deviceRouteController = new LegacyDeviceRouteController(
mContext,
mAudioManager,
mAudioService,
@@ -269,7 +269,7 @@ public class DeviceRouteControllerTest {
@Captor
private ArgumentCaptor<IAudioRoutesObserver.Stub> mAudioRoutesObserverCaptor;
- private DeviceRouteController mDeviceRouteController;
+ private LegacyDeviceRouteController mDeviceRouteController;
private IAudioRoutesObserver.Stub mAudioRoutesObserver;
@Before
@@ -287,7 +287,7 @@ public class DeviceRouteControllerTest {
when(mAudioService.startWatchingRoutes(mAudioRoutesObserverCaptor.capture()))
.thenReturn(audioRoutesInfo);
- mDeviceRouteController = new DeviceRouteController(
+ mDeviceRouteController = new LegacyDeviceRouteController(
mContext,
mAudioManager,
mAudioService,
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index c6a7fbcfb454..ee4b839dda5a 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -572,41 +572,14 @@ public class BatteryStatsImplTest {
mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, true, 9000, 9000);
mBatteryStatsImpl.noteBluetoothScanResultsFromSourceLocked(ws, 42, 9000, 9000);
-
-
- final Parcel uidTrafficParcel1 = Parcel.obtain();
- final Parcel uidTrafficParcel2 = Parcel.obtain();
-
- uidTrafficParcel1.writeInt(10042);
- uidTrafficParcel1.writeLong(3000);
- uidTrafficParcel1.writeLong(4000);
- uidTrafficParcel1.setDataPosition(0);
- uidTrafficParcel2.writeInt(10043);
- uidTrafficParcel2.writeLong(5000);
- uidTrafficParcel2.writeLong(8000);
- uidTrafficParcel2.setDataPosition(0);
-
- List<UidTraffic> uidTrafficList = ImmutableList.of(
- UidTraffic.CREATOR.createFromParcel(uidTrafficParcel1),
- UidTraffic.CREATOR.createFromParcel(uidTrafficParcel2));
-
- final Parcel btActivityEnergyInfoParcel = Parcel.obtain();
- btActivityEnergyInfoParcel.writeLong(1000);
- btActivityEnergyInfoParcel.writeInt(
- BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE);
- btActivityEnergyInfoParcel.writeLong(9000);
- btActivityEnergyInfoParcel.writeLong(8000);
- btActivityEnergyInfoParcel.writeLong(12000);
- btActivityEnergyInfoParcel.writeLong(0);
- btActivityEnergyInfoParcel.writeTypedList(uidTrafficList);
- btActivityEnergyInfoParcel.setDataPosition(0);
-
- BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR
- .createFromParcel(btActivityEnergyInfoParcel);
-
- uidTrafficParcel1.recycle();
- uidTrafficParcel2.recycle();
- btActivityEnergyInfoParcel.recycle();
+ BluetoothActivityEnergyInfo info = createBluetoothActivityEnergyInfo(
+ /* timestamp= */ 1000,
+ /* controllerTxTimeMs= */ 9000,
+ /* controllerRxTimeMs= */ 8000,
+ /* controllerIdleTimeMs= */ 12000,
+ /* controllerEnergyUsed= */ 0,
+ createUidTraffic(/* appUid= */ 10042, /* rxBytes= */ 3000, /* txBytes= */ 4000),
+ createUidTraffic(/* appUid= */ 10043, /* rxBytes= */ 5000, /* txBytes= */ 8000));
mBatteryStatsImpl.updateBluetoothStateLocked(info, -1, 1000, 1000);
@@ -622,4 +595,105 @@ public class BatteryStatsImplTest {
assertThat(uidStats.rxTimeMs).isEqualTo(7375); // Some scan time is treated as RX
assertThat(uidStats.txTimeMs).isEqualTo(7666); // Some scan time is treated as TX
}
+
+ /** A regression test for b/266128651 */
+ @Test
+ public void testGetNetworkActivityBytes_multipleUpdates() {
+ when(mPowerProfile.getAveragePower(
+ PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0);
+ mBatteryStatsImpl.setOnBatteryInternal(true);
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+
+ BluetoothActivityEnergyInfo info1 = createBluetoothActivityEnergyInfo(
+ /* timestamp= */ 10000,
+ /* controllerTxTimeMs= */ 9000,
+ /* controllerRxTimeMs= */ 8000,
+ /* controllerIdleTimeMs= */ 2000,
+ /* controllerEnergyUsed= */ 0,
+ createUidTraffic(/* appUid= */ 10042, /* rxBytes= */ 3000, /* txBytes= */ 4000),
+ createUidTraffic(/* appUid= */ 10043, /* rxBytes= */ 5000, /* txBytes= */ 8000));
+
+ mBatteryStatsImpl.updateBluetoothStateLocked(info1, -1, 1000, 1000);
+
+ long totalRx1 = mBatteryStatsImpl.getNetworkActivityBytes(
+ BatteryStats.NETWORK_BT_RX_DATA, BatteryStats.STATS_SINCE_CHARGED);
+ long totalTx1 = mBatteryStatsImpl.getNetworkActivityBytes(
+ BatteryStats.NETWORK_BT_TX_DATA, BatteryStats.STATS_SINCE_CHARGED);
+
+ assertThat(totalRx1).isEqualTo(8000); // 3000 + 5000
+ assertThat(totalTx1).isEqualTo(12000); // 4000 + 8000
+
+ BluetoothActivityEnergyInfo info2 = createBluetoothActivityEnergyInfo(
+ /* timestamp= */ 20000,
+ /* controllerTxTimeMs= */ 19000,
+ /* controllerRxTimeMs= */ 18000,
+ /* controllerIdleTimeMs= */ 3000,
+ /* controllerEnergyUsed= */ 0,
+ createUidTraffic(/* appUid= */ 10043, /* rxBytes= */ 6000, /* txBytes= */ 9500),
+ createUidTraffic(/* appUid= */ 10044, /* rxBytes= */ 7000, /* txBytes= */ 9000));
+
+ mBatteryStatsImpl.updateBluetoothStateLocked(info2, -1, 2000, 2000);
+
+ long totalRx2 = mBatteryStatsImpl.getNetworkActivityBytes(
+ BatteryStats.NETWORK_BT_RX_DATA, BatteryStats.STATS_SINCE_CHARGED);
+ long totalTx2 = mBatteryStatsImpl.getNetworkActivityBytes(
+ BatteryStats.NETWORK_BT_TX_DATA, BatteryStats.STATS_SINCE_CHARGED);
+
+ assertThat(totalRx2).isEqualTo(16000); // 3000 + 6000 (updated) + 7000 (new)
+ assertThat(totalTx2).isEqualTo(22500); // 4000 + 9500 (updated) + 9000 (new)
+
+ BluetoothActivityEnergyInfo info3 = createBluetoothActivityEnergyInfo(
+ /* timestamp= */ 30000,
+ /* controllerTxTimeMs= */ 20000,
+ /* controllerRxTimeMs= */ 20000,
+ /* controllerIdleTimeMs= */ 4000,
+ /* controllerEnergyUsed= */ 0,
+ createUidTraffic(/* appUid= */ 10043, /* rxBytes= */ 7000, /* txBytes= */ 9900),
+ createUidTraffic(/* appUid= */ 10044, /* rxBytes= */ 8000, /* txBytes= */ 10000));
+
+ mBatteryStatsImpl.updateBluetoothStateLocked(info3, -1, 2000, 2000);
+
+ long totalRx3 = mBatteryStatsImpl.getNetworkActivityBytes(
+ BatteryStats.NETWORK_BT_RX_DATA, BatteryStats.STATS_SINCE_CHARGED);
+ long totalTx3 = mBatteryStatsImpl.getNetworkActivityBytes(
+ BatteryStats.NETWORK_BT_TX_DATA, BatteryStats.STATS_SINCE_CHARGED);
+
+ assertThat(totalRx3).isEqualTo(18000); // 3000 + 7000 (updated) + 8000 (updated)
+ assertThat(totalTx3).isEqualTo(23900); // 4000 + 9900 (updated) + 10000 (updated)
+ }
+
+ private UidTraffic createUidTraffic(int appUid, long rxBytes, long txBytes) {
+ final Parcel parcel = Parcel.obtain();
+ parcel.writeInt(appUid); // mAppUid
+ parcel.writeLong(rxBytes); // mRxBytes
+ parcel.writeLong(txBytes); // mTxBytes
+ parcel.setDataPosition(0);
+ UidTraffic uidTraffic = UidTraffic.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return uidTraffic;
+ }
+
+ private BluetoothActivityEnergyInfo createBluetoothActivityEnergyInfo(
+ long timestamp,
+ long controllerTxTimeMs,
+ long controllerRxTimeMs,
+ long controllerIdleTimeMs,
+ long controllerEnergyUsed,
+ UidTraffic... uidTraffic) {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeLong(timestamp); // mTimestamp
+ parcel.writeInt(
+ BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE); // mBluetoothStackState
+ parcel.writeLong(controllerTxTimeMs); // mControllerTxTimeMs;
+ parcel.writeLong(controllerRxTimeMs); // mControllerRxTimeMs;
+ parcel.writeLong(controllerIdleTimeMs); // mControllerIdleTimeMs;
+ parcel.writeLong(controllerEnergyUsed); // mControllerEnergyUsed;
+ parcel.writeTypedList(ImmutableList.copyOf(uidTraffic)); // mUidTraffic
+ parcel.setDataPosition(0);
+
+ BluetoothActivityEnergyInfo info =
+ BluetoothActivityEnergyInfo.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return info;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index 34e45c2096ea..397d7b5f2a3e 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -18,9 +18,9 @@ package com.android.server.power.stats;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
-import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_RETENTION_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -45,6 +45,7 @@ import java.util.concurrent.ThreadLocalRandom;
@RunWith(AndroidJUnit4.class)
public class CpuWakeupStatsTest {
private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device";
+ private static final String KERNEL_REASON_WIFI_IRQ = "120 test.wifi.device";
private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device";
private static final String KERNEL_REASON_UNSUPPORTED = "-1 test.alarm.device";
@@ -64,28 +65,29 @@ public class CpuWakeupStatsTest {
public void removesOldWakeups() {
// The xml resource doesn't matter for this test.
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1, mHandler);
+ final long retention = obj.mConfig.WAKEUP_STATS_RETENTION_MS;
final Set<Long> timestamps = new HashSet<>();
final long firstWakeup = 453192;
- obj.noteWakeupTimeAndReason(firstWakeup, 32, "unused");
+ obj.noteWakeupTimeAndReason(firstWakeup, 32, KERNEL_REASON_UNKNOWN_IRQ);
timestamps.add(firstWakeup);
for (int i = 1; i < 1000; i++) {
- final long delta = mRandom.nextLong(WAKEUP_RETENTION_MS);
+ final long delta = mRandom.nextLong(retention);
if (timestamps.add(firstWakeup + delta)) {
- obj.noteWakeupTimeAndReason(firstWakeup + delta, i, "unused");
+ obj.noteWakeupTimeAndReason(firstWakeup + delta, i, KERNEL_REASON_UNKNOWN_IRQ);
}
}
assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
- obj.noteWakeupTimeAndReason(firstWakeup + WAKEUP_RETENTION_MS + 1242, 231, "unused");
+ obj.noteWakeupTimeAndReason(firstWakeup + retention + 1242, 231,
+ KERNEL_REASON_UNKNOWN_IRQ);
assertThat(obj.mWakeupEvents.size()).isEqualTo(timestamps.size());
for (int i = 0; i < 100; i++) {
- final long now = mRandom.nextLong(WAKEUP_RETENTION_MS + 1, 100 * WAKEUP_RETENTION_MS);
- obj.noteWakeupTimeAndReason(now, i, "unused");
- assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - WAKEUP_RETENTION_MS))
- .isLessThan(0);
+ final long now = mRandom.nextLong(retention + 1, 100 * retention);
+ obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_UNKNOWN_IRQ);
+ assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0);
}
}
@@ -111,17 +113,45 @@ public class CpuWakeupStatsTest {
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(false);
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
}
@Test
- public void alarmIrqAttributionCombined() {
+ public void wifiIrqAttributionSolo() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+ final long wakeupTime = 12423121;
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_WIFI_IRQ);
+
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
+ wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
+ wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 3, TEST_UID_4, TEST_UID_5);
+
+ final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(1);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(true);
+ }
+
+ @Test
+ public void alarmAndWifiIrqAttribution() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123210;
obj.noteWakeupTimeAndReason(wakeupTime, 4,
- KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_ALARM_IRQ);
+ KERNEL_REASON_WIFI_IRQ + ":" + KERNEL_REASON_ALARM_IRQ);
+ // Alarm activity
// Outside the window, so should be ignored.
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM,
wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
@@ -132,16 +162,34 @@ public class CpuWakeupStatsTest {
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
TEST_UID_5);
+ // Wifi activity
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
+ wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_4);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI,
+ wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_3);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 2, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 1, TEST_UID_2,
+ TEST_UID_5);
+
final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
assertThat(attribution).isNotNull();
assertThat(attribution.size()).isEqualTo(2);
+
assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_1)).isEqualTo(false);
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_2)).isEqualTo(false);
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(true);
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(true);
assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_5)).isEqualTo(true);
- assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_WIFI)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_1)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_2)).isEqualTo(true);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_3)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_4)).isEqualTo(false);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_WIFI).get(TEST_UID_5)).isEqualTo(true);
}
@Test
@@ -151,9 +199,11 @@ public class CpuWakeupStatsTest {
obj.noteWakeupTimeAndReason(wakeupTime, 24, KERNEL_REASON_UNKNOWN_IRQ);
+ assertThat(obj.mWakeupEvents.size()).isEqualTo(1);
+
// Unrelated subsystems, should not be attributed
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
- obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4,
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 3, TEST_UID_4,
TEST_UID_5);
final SparseArray<SparseBooleanArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
@@ -165,42 +215,48 @@ public class CpuWakeupStatsTest {
}
@Test
- public void unknownAttribution() {
+ public void unknownWakeupIgnored() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 72123210;
obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
- // Should be ignored as this type of wakeup is unsupported.
+ // Should be ignored as this type of wakeup is not known.
+ assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
+
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
- // There should be nothing in the attribution map.
+ // Any nearby activity should not end up in the attribution map.
assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
}
@Test
- public void unsupportedAttribution() {
+ public void unsupportedWakeupIgnored() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
long wakeupTime = 970934;
obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED);
// Should be ignored as this type of wakeup is unsupported.
+ assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
+
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
- // There should be nothing in the attribution map.
+ // Any nearby activity should not end up in the attribution map.
assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
wakeupTime = 883124;
obj.noteWakeupTimeAndReason(wakeupTime, 3, KERNEL_REASON_ABORT);
// Should be ignored as this type of wakeup is unsupported.
- obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 2, TEST_UID_1, TEST_UID_4);
- obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 5, TEST_UID_3);
+ assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
+
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime + 2, TEST_UID_1, TEST_UID_4);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_WIFI, wakeupTime - 5, TEST_UID_3);
- // There should be nothing in the attribution map.
+ // Any nearby activity should not end up in the attribution map.
assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 6f37e601abce..ce076217f37b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -24,6 +24,8 @@ import static android.service.notification.NotificationListenerService.META_DATA
import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -1201,28 +1203,11 @@ public class ManagedServicesTest extends UiServiceTestCase {
mIpm, approvalLevel);
loadXml(service);
- List<String> allowedPackagesForUser0 = new ArrayList<>();
- allowedPackagesForUser0.add("this.is.a.package.name");
- allowedPackagesForUser0.add("another.package");
- allowedPackagesForUser0.add("secondary");
-
- List<String> actual = service.getAllowedPackages(0);
- assertEquals(3, actual.size());
- for (String pkg : allowedPackagesForUser0) {
- assertTrue(actual.contains(pkg));
- }
-
- List<String> allowedPackagesForUser10 = new ArrayList<>();
- allowedPackagesForUser10.add("this.is.another.package");
- allowedPackagesForUser10.add("package");
- allowedPackagesForUser10.add("this.is.another.package");
- allowedPackagesForUser10.add("component");
-
- actual = service.getAllowedPackages(10);
- assertEquals(4, actual.size());
- for (String pkg : allowedPackagesForUser10) {
- assertTrue(actual.contains(pkg));
- }
+ assertThat(service.getAllowedPackages(0)).containsExactly("this.is.a.package.name",
+ "another.package", "secondary");
+ assertThat(service.getAllowedPackages(10)).containsExactly("this.is.another.package",
+ "package", "this.is.another.package", "component");
+ assertThat(service.getAllowedPackages(999)).isEmpty();
}
}
@@ -1263,31 +1248,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
- public void testGetAllowedPackages() throws Exception {
- ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
- mIpm, APPROVAL_BY_COMPONENT);
- loadXml(service);
- service.mApprovalLevel = APPROVAL_BY_PACKAGE;
- loadXml(service);
-
- List<String> allowedPackages = new ArrayList<>();
- allowedPackages.add("this.is.a.package.name");
- allowedPackages.add("another.package");
- allowedPackages.add("secondary");
- allowedPackages.add("this.is.another.package");
- allowedPackages.add("package");
- allowedPackages.add("component");
- allowedPackages.add("bananas!");
- allowedPackages.add("non.user.set.package");
-
- Set<String> actual = service.getAllowedPackages();
- assertEquals(allowedPackages.size(), actual.size());
- for (String pkg : allowedPackages) {
- assertTrue(actual.contains(pkg));
- }
- }
-
- @Test
public void testOnUserRemoved() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
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 f08d0f5f71a4..354420f46c2f 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -29,6 +29,7 @@ import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.NotificationChannel.USER_LOCKED_ALLOW_BUBBLE;
+import static android.app.NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -50,7 +51,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -237,6 +237,7 @@ import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
+import com.google.android.collect.Lists;
import com.google.common.collect.ImmutableList;
import org.junit.After;
@@ -245,10 +246,13 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.ArgumentMatchers;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.BufferedInputStream;
@@ -440,6 +444,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager);
mContext.addMockSystemService(NotificationManager.class, mMockNm);
+ doNothing().when(mContext).sendBroadcastAsUser(any(), any());
doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any());
setDpmAppOppsExemptFromDismissal(false);
@@ -7828,6 +7833,75 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void onZenModeChanged_sendsBroadcasts() throws Exception {
+ when(mAmi.getCurrentUserId()).thenReturn(100);
+ when(mUmInternal.getProfileIds(eq(100), anyBoolean())).thenReturn(new int[]{100, 101, 102});
+ when(mConditionProviders.getAllowedPackages(anyInt())).then(new Answer<List<String>>() {
+ @Override
+ public List<String> answer(InvocationOnMock invocation) {
+ int userId = invocation.getArgument(0);
+ switch (userId) {
+ case 100:
+ return Lists.newArrayList("a", "b", "c");
+ case 101:
+ return Lists.newArrayList();
+ case 102:
+ return Lists.newArrayList("b");
+ default:
+ throw new IllegalArgumentException(
+ "Why would you ask for packages of userId " + userId + "?");
+ }
+ }
+ });
+
+ mService.getBinderService().setZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS, null,
+ "testing!");
+ waitForIdle();
+
+ InOrder inOrder = inOrder(mContext);
+ // Verify broadcasts for registered receivers
+ inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(
+ new Intent(ACTION_INTERRUPTION_FILTER_CHANGED).setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY)), eq(UserHandle.of(100)), eq(null));
+ inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(
+ new Intent(ACTION_INTERRUPTION_FILTER_CHANGED).setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY)), eq(UserHandle.of(101)), eq(null));
+ inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(
+ new Intent(ACTION_INTERRUPTION_FILTER_CHANGED).setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY)), eq(UserHandle.of(102)), eq(null));
+
+ // Verify broadcast for packages that manage DND.
+ inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent(
+ ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("a").setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(100)));
+ inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent(
+ ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("b").setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(100)));
+ inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent(
+ ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("c").setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(100)));
+ inOrder.verify(mContext).sendBroadcastAsUser(eqIntent(new Intent(
+ ACTION_INTERRUPTION_FILTER_CHANGED).setPackage("b").setFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)), eq(UserHandle.of(102)));
+ }
+
+ private static Intent eqIntent(Intent wanted) {
+ return ArgumentMatchers.argThat(
+ new ArgumentMatcher<Intent>() {
+ @Override
+ public boolean matches(Intent argument) {
+ return wanted.filterEquals(argument)
+ && wanted.getFlags() == argument.getFlags();
+ }
+
+ @Override
+ public String toString() {
+ return wanted.toString();
+ }
+ });
+ }
+
+ @Test
public void testAreNotificationsEnabledForPackage() throws Exception {
mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
mUid);
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 6661e6a0a60d..49f215a83c2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2897,7 +2897,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Make the top one invisible, and try transferring the starting window from the top to the
// bottom one.
- activityTop.setVisibility(false, false);
+ activityTop.setVisibility(false);
activityBottom.transferStartingWindowFromHiddenAboveTokenIfNeeded();
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 4d71b30d71e2..6d1312458ce2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -165,7 +165,7 @@ public class AppChangeTransitionTests extends WindowTestsBase {
assertTrue(mTask.isInChangeTransition());
// Changing visibility should cancel the change transition and become closing
- mActivity.setVisibility(false, false);
+ mActivity.setVisibility(false);
assertEquals(0, mDisplayContent.mChangingContainers.size());
assertFalse(mTask.isInChangeTransition());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 6b814e678419..59cc4f5b0948 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -295,8 +295,8 @@ public class AppTransitionTests extends WindowTestsBase {
dc2.prepareAppTransition(TRANSIT_CLOSE);
// One activity window is visible for resuming & the other activity window is invisible
// for finishing in different display.
- activity1.setVisibility(true, false);
- activity2.setVisibility(false, false);
+ activity1.setVisibility(true);
+ activity2.setVisibility(false);
// Make sure each display is in animating stage.
assertTrue(dc1.mOpeningApps.size() > 0);
@@ -365,7 +365,7 @@ public class AppTransitionTests extends WindowTestsBase {
dc.prepareAppTransition(TRANSIT_CLOSE);
assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_CLOSE));
dc.mAppTransition.overridePendingAppTransitionRemote(adapter);
- exitingActivity.setVisibility(false, false);
+ exitingActivity.setVisibility(false);
assertTrue(dc.mClosingApps.size() > 0);
// Make sure window is in animating stage before freeze, and cancel after freeze.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index aaeae239024d..3379bebe6698 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1620,7 +1620,6 @@ public class DisplayContentTests extends WindowTestsBase {
// If the rotated activity requests to show IME, the IME window should use the
// transformation from activity to lay out in the same orientation.
- mDisplayContent.setImeLayeringTarget(mAppWindow);
LocalServices.getService(WindowManagerInternal.class).onToggleImeRequested(true /* show */,
app.token, app.token, mDisplayContent.mDisplayId);
assertTrue(asyncRotationController.isTargetToken(mImeWindow.mToken));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index df3af7d2f4fa..49d8da1b2880 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -351,8 +351,8 @@ public class TaskFragmentTest extends WindowTestsBase {
final TaskFragment taskFragment1 = createTaskFragmentWithEmbeddedActivity(task, mOrganizer);
final ActivityRecord activity0 = taskFragment0.getTopMostActivity();
final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
- activity0.setVisibility(true /* visible */, false /* deferHidingClient */);
- activity1.setVisibility(true /* visible */, false /* deferHidingClient */);
+ activity0.setVisibility(true);
+ activity1.setVisibility(true);
spyOn(mAtm.mTaskFragmentOrganizerController);
// Move activity to pinned.
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 2fccb88ad8de..75a8dd822914 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -92,11 +92,11 @@ public class UnknownAppVisibilityControllerTest extends WindowTestsBase {
@Test
public void testRemoveFinishingInvisibleActivityFromUnknown() {
- final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
activity.finishing = true;
activity.setVisibleRequested(true);
- activity.setVisibility(false, false);
+ activity.setVisibility(false);
assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 3aee2cd5fac1..33067df0c8f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -914,15 +914,15 @@ public class WindowStateTests extends WindowTestsBase {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app", uid);
app.mActivityRecord.setVisible(false);
- app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+ app.mActivityRecord.setVisibility(false);
assertFalse(mAtm.hasActiveVisibleWindow(uid));
- app.mActivityRecord.setVisibility(true /* visible */, false /* deferHidingClient */);
+ app.mActivityRecord.setVisibility(true);
assertTrue(mAtm.hasActiveVisibleWindow(uid));
// Make the activity invisible and add a visible toast. The uid should have no active
// visible window because toast can be misused by legacy app to bypass background check.
- app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+ app.mActivityRecord.setVisibility(false);
final WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay", uid);
final WindowState toast = createWindow(null, TYPE_TOAST, app.mToken, "toast", uid);
toast.onSurfaceShownChanged(true);
@@ -1157,12 +1157,12 @@ public class WindowStateTests extends WindowTestsBase {
public void testRequestedVisibility() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
app.mActivityRecord.setVisible(false);
- app.mActivityRecord.setVisibility(false /* visible */, false /* deferHidingClient */);
+ app.mActivityRecord.setVisibility(false);
assertFalse(app.isVisibleRequested());
// It doesn't have a surface yet, but should still be visible requested.
app.setHasSurface(false);
- app.mActivityRecord.setVisibility(true /* visible */, false /* deferHidingClient */);
+ app.mActivityRecord.setVisibility(true);
assertFalse(app.isVisible());
assertTrue(app.isVisibleRequested());
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
index 2c5fcb86b0c5..68067d27a0a5 100644
--- a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -191,9 +191,9 @@ public final class UsbGadgetHidl implements UsbGadgetHal {
public void reset(long transactionId) {
try {
synchronized(mGadgetProxyLock) {
- if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
- android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+ if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
+ android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy);
gadgetProxy.reset();
}
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 918ae7901dbe..bf12b9cce302 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -7608,6 +7608,55 @@ public class CarrierConfigManager {
public static final String KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL =
KEY_PREFIX + "emergency_requires_volte_enabled_bool";
+ /**
+ * This values indicates that the cross SIM redialing timer shall be disabled.
+ *
+ * @see #KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT
+ * @see #KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT
+ * @hide
+ */
+ public static final int REDIAL_TIMER_DISABLED = 0;
+
+ /**
+ * A timer to guard the max attempting time on current SIM slot so that modem will not
+ * stuck in current SIM slot for long time. On timer expiry, if emergency call on the
+ * other SIM slot is preferable, telephony shall cancel the emergency call and place the
+ * call on the other SIM slot. If this value is set to {@link #REDIAL_TIMER_DISABLED}, then
+ * the timer will never be started and domain selection continues on the current SIM slot.
+ * This value should be greater than the value of {@link #KEY_EMERGENCY_SCAN_TIMER_SEC_INT}.
+ *
+ * The default value for the timer is 120 seconds.
+ * @hide
+ */
+ public static final String KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT =
+ KEY_PREFIX + "cross_stack_redial_timer_sec_int";
+
+ /**
+ * If emergency calls are only allowed with normal-registered service and UE should get
+ * normal service in a short time with acquired band information, telephony
+ * expects dialing emergency call will be completed in a short time.
+ * If dialing is not completed with in a certain timeout, telephony shall place on
+ * another SIM slot. If this value is set to {@link #REDIAL_TIMER_DISABLED}, then the timer
+ * will never be started and domain selection continues on the current SIM slot.
+ * The timer shall be started for the first trial of each subscription and shall be ignored
+ * in the roaming networks and non-domestic networks.
+ *
+ * The default value for the timer is {@link #REDIAL_TIMER_DISABLED}.
+ * @hide
+ */
+ public static final String KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT =
+ KEY_PREFIX + "quick_cross_stack_redial_timer_sec_int";
+
+ /**
+ * Indicates whether the quick cross stack redial timer will be triggered only when
+ * the device is registered to the network.
+ *
+ * The default value is {@code true}.
+ * @hide
+ */
+ public static final String KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL =
+ KEY_PREFIX + "start_quick_cross_stack_redial_timer_when_registered_bool";
+
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
defaults.putBoolean(KEY_RETRY_EMERGENCY_ON_IMS_PDN_BOOL, false);
@@ -7674,6 +7723,10 @@ public class CarrierConfigManager {
defaults.putBoolean(KEY_EMERGENCY_REQUIRES_VOLTE_ENABLED_BOOL, false);
defaults.putStringArray(KEY_EMERGENCY_CDMA_PREFERRED_NUMBERS_STRING_ARRAY,
new String[0]);
+ defaults.putInt(KEY_CROSS_STACK_REDIAL_TIMER_SEC_INT, 120);
+ defaults.putInt(KEY_QUICK_CROSS_STACK_REDIAL_TIMER_SEC_INT, REDIAL_TIMER_DISABLED);
+ defaults.putBoolean(KEY_START_QUICK_CROSS_STACK_REDIAL_TIMER_WHEN_REGISTERED_BOOL,
+ true);
return defaults;
}
diff --git a/telephony/java/android/telephony/PreciseDisconnectCause.java b/telephony/java/android/telephony/PreciseDisconnectCause.java
index 3b4cf75e7919..1cfd22cc4d65 100644
--- a/telephony/java/android/telephony/PreciseDisconnectCause.java
+++ b/telephony/java/android/telephony/PreciseDisconnectCause.java
@@ -235,6 +235,23 @@ public final class PreciseDisconnectCause {
/** Call failed/dropped because of a network detach. */
public static final int NETWORK_DETACH = 261;
+ /**
+ * Dialing emergency calls is currently unavailable.
+ * The call should be redialed on the other subscription silently.
+ * If there is no other subscription available, the call may be redialed
+ * on this subscription again.
+ * @hide
+ */
+ public static final int EMERGENCY_TEMP_FAILURE = 325;
+ /**
+ * Dialing emergency calls is currently unavailable.
+ * The call should be redialed on the other subscription silently.
+ * Even if there is no other subscription available, the call should not
+ * be redialed on this subscription again.
+ * @hide
+ */
+ public static final int EMERGENCY_PERM_FAILURE = 326;
+
/** Mobile station (MS) is locked until next power cycle. */
public static final int CDMA_LOCKED_UNTIL_POWER_CYCLE = 1000;
/** Drop call. */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 9dc4bf034e66..9c3460c124ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -317,7 +317,8 @@ fun FlickerTest.replacesLayer(
assertion.then().isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
}
if (ignoreSplashscreen) {
- assertion.then().isSplashScreenVisibleFor(newLayer, isOptional = true)
+ assertion.then().isSplashScreenVisibleFor(
+ ComponentNameMatcher(newLayer.packageName, className = ""), isOptional = true)
}
assertion.then().isVisible(newLayer)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 19ecf6ab8799..05abf9fd1a8e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -53,7 +53,7 @@ import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
+open class OpenAppColdFromIcon(flicker: FlickerTest) : OpenAppFromLauncherTransition(flicker) {
/** {@inheritDoc} */
override val transition: FlickerBuilder.() -> Unit
get() = {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
new file mode 100644
index 000000000000..46899f373fcf
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.tools.common.NavBar
+import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/** Some assertions will fail because of b/264415996 */
+@FlickerServiceCompatible
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppColdFromIconCfArm(flicker: FlickerTest) : OpenAppColdFromIcon(flicker) {
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ // TAPL fails on landscape mode b/240916028
+ return FlickerTestFactory.nonRotationTests(
+ supportedNavigationModes = listOf(NavBar.MODE_3BUTTON)
+ )
+ }
+ }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
index 6005a81aac9e..786bb32096ad 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt
@@ -16,9 +16,10 @@
package com.android.server.wm.flicker.launch
+import android.os.SystemClock
import android.platform.test.annotations.Postsubmit
import android.tools.device.apphelpers.CameraAppHelper
-import android.tools.device.flicker.annotation.FlickerServiceCompatible
+import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
@@ -54,13 +55,14 @@ import org.junit.runners.Parameterized
* ```
*/
@RequiresDevice
-@FlickerServiceCompatible
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) :
OpenAppFromLauncherTransition(flicker) {
private val cameraApp = CameraAppHelper(instrumentation)
+ override val testApp: StandardAppHelper
+ get() = cameraApp
override val transition: FlickerBuilder.() -> Unit
get() = {
@@ -70,6 +72,7 @@ class OpenCameraOnDoubleClickPowerButton(flicker: FlickerTest) :
}
transitions {
device.pressKeyCode(KeyEvent.KEYCODE_POWER)
+ SystemClock.sleep(100)
device.pressKeyCode(KeyEvent.KEYCODE_POWER)
wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(cameraApp).waitForAndVerify()
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
index da985232a1e3..b848e63c9c87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt
@@ -93,8 +93,7 @@ class OverrideTaskTransitionTest(val flicker: FlickerTest) {
.then()
// Animation starts, but the app may not be drawn yet which means the Splash
// may be visible.
- .isInvisible(testApp, isOptional = true)
- .isVisible(ComponentNameMatcher.SPLASH_SCREEN, isOptional = true)
+ .isSplashScreenVisibleFor(testApp, isOptional = true)
.then()
// App shows up with the custom animation starting at alpha=1.
.isVisible(testApp)
diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
index 8b1b06fe40e8..24a567130ff0 100644
--- a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
+++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
@@ -124,14 +124,12 @@ class MotionPredictorTest {
predictor.record(moveEvent)
val predicted = predictor.predict(Duration.ofMillis(8).toNanos())
- assertEquals(1, predicted.size)
- val event = predicted[0]
- assertNotNull(event)
+ assertNotNull(predicted)
// Prediction will happen for t=12 (since it is the next input interval after the requested
// time, 8, plus the model offset, 1).
- assertEquals(12, event.eventTime)
- assertEquals(30f, event.x, /*delta=*/5f)
- assertEquals(60f, event.y, /*delta=*/15f)
+ assertEquals(12, predicted!!.eventTime)
+ assertEquals(30f, predicted.x, /*delta=*/5f)
+ assertEquals(60f, predicted.y, /*delta=*/15f)
}
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
index 573b3b695a90..d2708ad47712 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java
@@ -42,6 +42,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import org.junit.After;
@@ -74,7 +75,7 @@ public final class NotificationTest {
// This is for AOSP System UI for phones. When testing customized System UI, please modify here.
private static final BySelector REPLY_SEND_BUTTON_SELECTOR =
- By.res("com.android.systemui", "remote_input_send");
+ By.res("com.android.systemui", "remote_input_send").enabled(true);
@Rule
public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
@@ -119,7 +120,15 @@ public final class NotificationTest {
mUiDevice.pressKeyCode(KeyEvent.KEYCODE_A);
mUiDevice.pressKeyCode(KeyEvent.KEYCODE_B);
mUiDevice.pressKeyCode(KeyEvent.KEYCODE_C);
- mUiDevice.wait(Until.findObject(REPLY_SEND_BUTTON_SELECTOR.enabled(true)), TIMEOUT).click();
+ UiObject2 sendButton = mUiDevice.wait(
+ Until.findObject(REPLY_SEND_BUTTON_SELECTOR), TIMEOUT);
+ if (sendButton == null) {
+ // If the screen is too small, sendButton may be hidden by IME.
+ // Dismiss IME and try again.
+ mUiDevice.pressBack();
+ sendButton = mUiDevice.wait(Until.findObject(REPLY_SEND_BUTTON_SELECTOR), TIMEOUT);
+ }
+ sendButton.click();
// Verify that IME is gone.
assertThat(mUiDevice.wait(Until.gone(By.pkg(getImePackage(mContext))), TIMEOUT)).isTrue();
}
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 2cdb94526839..7deb8c73d1fc 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -86,7 +86,7 @@ public class ProtoLogImplTest {
mFile = testContext.getFileStreamPath("tracing_test.dat");
//noinspection ResultOfMethodCallIgnored
mFile.delete();
- mProtoLog = new ProtoLogImpl(mFile, 1024 * 1024, mReader);
+ mProtoLog = new ProtoLogImpl(mFile, 1024 * 1024, mReader, 1024);
}
@After
diff --git a/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
index f529bf77f32a..229d0c8da6e9 100644
--- a/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
+++ b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
@@ -97,8 +97,8 @@ class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs)
}
// Draw predictions. Convert to nanos and hardcode to +20ms into the future
- val predictionList = predictor.predict(eventTime * 1000000 + 20000000)
- for (prediction in predictionList) {
+ val prediction = predictor.predict(eventTime * 1000000 + 20000000)
+ if (prediction != null) {
val realEvents = events.get(prediction.deviceId)!!
drawLine(canvas, realEvents[realEvents.size - 1], prediction, predictionPaint)
}
diff --git a/tools/codegen/Android.bp b/tools/codegen/Android.bp
index e53ba3e18a86..a1df878df12e 100644
--- a/tools/codegen/Android.bp
+++ b/tools/codegen/Android.bp
@@ -9,7 +9,7 @@ package {
java_binary_host {
name: "codegen_cli",
- manifest: "manifest.txt",
+ main_class: "com.android.codegen.MainKt",
srcs: [
"src/**/*.kt",
],
diff --git a/tools/codegen/BUILD.bazel b/tools/codegen/BUILD.bazel
deleted file mode 100644
index c14046d674dc..000000000000
--- a/tools/codegen/BUILD.bazel
+++ /dev/null
@@ -1,21 +0,0 @@
-# TODO(b/245731902): auto-generate these with bp2build.
-load("@rules_kotlin//kotlin:jvm_library.bzl", "kt_jvm_library")
-
-java_binary(
- name = "codegen_cli",
- main_class = "com.android.codegen.MainKt",
- runtime_deps = [
- ":codegen_cli_kt_lib",
- ],
-)
-
-kt_jvm_library(
- name = "codegen_cli_kt_lib",
- srcs = glob(["src/**/*.kt"]),
- deps = ["//external/javaparser"],
-)
-
-kt_jvm_library(
- name = "codegen-version-info",
- srcs = glob(["src/**/SharedConstants.kt"]),
-)
diff --git a/tools/codegen/manifest.txt b/tools/codegen/manifest.txt
deleted file mode 100644
index 6e1018ba6b55..000000000000
--- a/tools/codegen/manifest.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-class: com.android.codegen.MainKt