summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp46
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java5
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java60
-rw-r--r--apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java10
-rw-r--r--apex/jobscheduler/framework/aconfig/job.aconfig7
-rw-r--r--apex/jobscheduler/service/aconfig/job.aconfig7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java19
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java10
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java14
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java66
-rw-r--r--core/api/Android.bp18
-rw-r--r--core/api/current.txt75
-rw-r--r--core/api/system-current.txt30
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ActivityThread.java7
-rw-r--r--core/java/android/app/AppOpsManager.java23
-rw-r--r--core/java/android/app/BackgroundInstallControlManager.java102
-rw-r--r--core/java/android/app/HomeVisibilityListener.java5
-rw-r--r--core/java/android/app/IProcessObserver.aidl11
-rw-r--r--core/java/android/app/Notification.java28
-rw-r--r--core/java/android/app/OWNERS4
-rw-r--r--core/java/android/app/WallpaperManager.java30
-rw-r--r--core/java/android/companion/virtual/IVirtualDeviceManager.aidl6
-rw-r--r--core/java/android/companion/virtual/camera/VirtualCameraConfig.java13
-rw-r--r--core/java/android/content/pm/PackageManager.java6
-rw-r--r--core/java/android/content/pm/UserInfo.java4
-rw-r--r--core/java/android/content/pm/flags.aconfig22
-rw-r--r--core/java/android/content/pm/multiuser.aconfig8
-rw-r--r--core/java/android/content/res/FontScaleConverterFactory.java10
-rw-r--r--core/java/android/credentials/ui/AuthenticationEntry.java57
-rw-r--r--core/java/android/credentials/ui/BaseDialogResult.java33
-rw-r--r--core/java/android/credentials/ui/CancelUiRequest.java17
-rw-r--r--core/java/android/credentials/ui/Constants.java4
-rw-r--r--core/java/android/credentials/ui/CreateCredentialProviderData.java11
-rw-r--r--core/java/android/credentials/ui/CreateCredentialProviderInfo.java113
-rw-r--r--core/java/android/credentials/ui/DisabledProviderData.java10
-rw-r--r--core/java/android/credentials/ui/DisabledProviderInfo.java50
-rw-r--r--core/java/android/credentials/ui/Entry.java44
-rw-r--r--core/java/android/credentials/ui/FailureDialogResult.java97
-rw-r--r--core/java/android/credentials/ui/FailureResult.java96
-rw-r--r--core/java/android/credentials/ui/GetCredentialProviderData.java48
-rw-r--r--core/java/android/credentials/ui/GetCredentialProviderInfo.java168
-rw-r--r--core/java/android/credentials/ui/IntentFactory.java19
-rw-r--r--core/java/android/credentials/ui/IntentHelper.java97
-rw-r--r--core/java/android/credentials/ui/ProviderDialogResult.java100
-rw-r--r--core/java/android/credentials/ui/ProviderPendingIntentResponse.java28
-rw-r--r--core/java/android/credentials/ui/RequestInfo.java17
-rw-r--r--core/java/android/credentials/ui/ResultHelper.java62
-rw-r--r--core/java/android/credentials/ui/UiResult.java24
-rw-r--r--core/java/android/credentials/ui/UserSelectionResult.java84
-rw-r--r--core/java/android/hardware/HardwareBuffer.java10
-rw-r--r--core/java/android/hardware/biometrics/AuthenticationStateListener.aidl16
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java20
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItemBulletedText.java10
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItemPlainText.java10
-rw-r--r--core/java/android/hardware/biometrics/PromptInfo.java4
-rw-r--r--core/java/android/hardware/biometrics/PromptVerticalListContentView.java16
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl9
-rw-r--r--core/java/android/hardware/input/InputSettings.java87
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig7
-rw-r--r--core/java/android/metrics/LogMaker.java1
-rw-r--r--core/java/android/net/thread/OWNERS3
-rw-r--r--core/java/android/net/thread/flags.aconfig8
-rwxr-xr-xcore/java/android/os/Build.java4
-rw-r--r--core/java/android/os/PersistableBundle.java37
-rw-r--r--core/java/android/os/Process.java10
-rw-r--r--core/java/android/os/SystemProperties.java31
-rw-r--r--core/java/android/os/Trace.java17
-rw-r--r--core/java/android/os/UserManager.java56
-rw-r--r--core/java/android/provider/Settings.java46
-rw-r--r--core/java/android/service/autofill/AutofillService.java23
-rw-r--r--core/java/android/service/autofill/ConvertCredentialCallback.java65
-rw-r--r--core/java/android/service/autofill/ConvertCredentialRequest.aidl19
-rw-r--r--core/java/android/service/autofill/ConvertCredentialRequest.java158
-rw-r--r--core/java/android/service/autofill/ConvertCredentialResponse.aidl19
-rw-r--r--core/java/android/service/autofill/ConvertCredentialResponse.java157
-rw-r--r--core/java/android/service/autofill/FillEventHistory.java11
-rw-r--r--core/java/android/service/autofill/FillResponse.java37
-rw-r--r--core/java/android/service/autofill/IAutoFillService.aidl5
-rw-r--r--core/java/android/service/autofill/IConvertCredentialCallback.aidl31
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java4
-rw-r--r--core/java/android/service/notification/ZenPolicy.java4
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java37
-rw-r--r--core/java/android/util/Singleton.java1
-rw-r--r--core/java/android/view/AttachedSurfaceControl.java19
-rw-r--r--core/java/android/view/Display.java2
-rw-r--r--core/java/android/view/DisplayInfo.java1
-rw-r--r--core/java/android/view/IWindowSession.aidl2
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java37
-rw-r--r--core/java/android/view/InsetsSource.java116
-rw-r--r--core/java/android/view/InsetsState.java48
-rw-r--r--core/java/android/view/Surface.java4
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/view/WindowManager.java53
-rw-r--r--core/java/android/view/WindowManagerGlobal.java98
-rw-r--r--core/java/android/view/WindowManagerImpl.java20
-rw-r--r--core/java/android/view/WindowlessWindowManager.java3
-rw-r--r--core/java/android/view/autofill/AutofillManager.java9
-rw-r--r--core/java/android/view/autofill/OWNERS1
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSessionV2.java7
-rw-r--r--core/java/android/view/flags/view_flags.aconfig9
-rw-r--r--core/java/android/window/ScreenCapture.java11
-rw-r--r--core/java/android/window/TaskFragmentCreationParams.java36
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java22
-rw-r--r--core/java/android/window/flags/large_screen_experiences_app_compat.aconfig16
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig8
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/java/com/android/internal/app/ConfirmUserCreationActivity.java12
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java6
-rw-r--r--core/java/com/android/internal/display/BrightnessSynchronizer.java2
-rw-r--r--core/java/com/android/internal/jank/Cuj.java10
-rw-r--r--core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java14
-rw-r--r--core/java/com/android/internal/logging/MetricsLogger.java1
-rw-r--r--core/java/com/android/internal/logging/testing/FakeMetricsLogger.java1
-rw-r--r--core/java/com/android/internal/logging/testing/UiEventLoggerFake.java1
-rw-r--r--core/java/com/android/internal/policy/SystemBarUtils.java33
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java37
-rw-r--r--core/java/com/android/internal/widget/ImageFloatingTextView.java63
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java6
-rw-r--r--core/java/com/android/internal/widget/MessagingLinearLayout.java30
-rw-r--r--core/java/com/android/internal/widget/PeopleHelper.java68
-rw-r--r--core/jni/android_util_Process.cpp45
-rw-r--r--core/jni/android_view_SurfaceControl.cpp4
-rw-r--r--core/jni/android_window_ScreenCapture.cpp6
-rw-r--r--core/proto/android/server/windowmanagerservice.proto1
-rw-r--r--core/res/AndroidManifest.xml48
-rw-r--r--core/res/res/color-night/notification_expand_button_state_tint.xml21
-rw-r--r--core/res/res/color/notification_expand_button_state_tint.xml11
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/xml/sms_short_codes.xml19
-rw-r--r--core/tests/InputMethodCoreTests/Android.bp66
-rw-r--r--core/tests/InputMethodCoreTests/AndroidManifest.xml31
-rw-r--r--core/tests/InputMethodCoreTests/AndroidTest.xml38
-rw-r--r--core/tests/InputMethodCoreTests/OWNERS1
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta.xml (renamed from core/tests/coretests/res/xml/ime_meta.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml (renamed from core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml (renamed from core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml (renamed from core/tests/coretests/res/xml/ime_meta_sw_next.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml (renamed from core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml (renamed from core/tests/coretests/res/xml/ime_meta_vr_only.xml)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java)2
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java (renamed from core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt (renamed from core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java)0
-rw-r--r--core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java (renamed from core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java)0
-rw-r--r--core/tests/coretests/Android.bp6
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageManagerTest.java49
-rw-r--r--core/tests/coretests/src/android/content/pm/UserInfoTest.java71
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt3
-rw-r--r--core/tests/coretests/src/android/os/BuildTest.java2
-rw-r--r--core/tests/coretests/src/android/os/TraceTest.java48
-rw-r--r--core/tests/coretests/src/android/telephony/PinResultTest.java34
-rw-r--r--core/tests/coretests/src/android/util/SingletonTest.java41
-rw-r--r--core/tests/coretests/src/android/view/DisplayInfoTest.java6
-rw-r--r--core/tests/coretests/src/android/view/InsetsStateTest.java8
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java66
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java10
-rw-r--r--core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java67
-rw-r--r--core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java80
-rw-r--r--core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java8
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java304
-rw-r--r--core/tests/systemproperties/Android.bp22
-rw-r--r--core/tests/systemproperties/src/android/os/SystemPropertiesTest.java29
-rw-r--r--core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java339
-rw-r--r--data/etc/privapp-permissions-platform.xml3
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--data/fonts/Android.bp6
-rw-r--r--data/fonts/Android.mk45
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java90
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java60
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java5
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java152
-rw-r--r--libs/WindowManager/Shell/multivalentTests/OWNERS4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java)14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java53
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java156
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java9
-rw-r--r--libs/hwui/Android.bp17
-rw-r--r--libs/hwui/utils/HostColorSpace.cpp417
-rw-r--r--location/java/android/location/altitude/AltitudeConverter.java191
-rw-r--r--location/java/com/android/internal/location/altitude/GeoidMap.java (renamed from location/java/com/android/internal/location/altitude/GeoidHeightMap.java)189
-rw-r--r--media/java/android/media/LoudnessCodecController.java15
-rw-r--r--media/java/android/media/LoudnessCodecDispatcher.java10
-rw-r--r--media/java/android/media/MediaRouter2.java32
-rw-r--r--media/java/android/media/RoutingSessionInfo.java8
-rw-r--r--media/java/android/media/flags/editing.aconfig8
-rw-r--r--media/java/android/media/metrics/EditingEndedEvent.aidl19
-rw-r--r--media/java/android/media/metrics/EditingEndedEvent.java325
-rw-r--r--media/java/android/media/metrics/EditingSession.java13
-rw-r--r--media/java/android/media/metrics/IMediaMetricsManager.aidl5
-rw-r--r--media/java/android/media/metrics/MediaMetricsManager.java14
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl5
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl2
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java14
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppManager.java46
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppService.java43
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppView.java17
-rw-r--r--media/jni/soundpool/StreamManager.cpp3
-rw-r--r--media/jni/soundpool/StreamManager.h2
-rw-r--r--media/jni/soundpool/android_media_SoundPool.cpp13
-rw-r--r--media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java2
-rw-r--r--native/graphics/jni/Android.bp16
-rw-r--r--packages/CrashRecovery/aconfig/flags.aconfig9
-rw-r--r--packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml2
-rw-r--r--packages/CredentialManager/res/drawable/more_options_list_item.xml31
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml42
-rw-r--r--packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml9
-rw-r--r--packages/CredentialManager/res/values/colors.xml2
-rw-r--r--packages/CredentialManager/res/values/dimens.xml8
-rw-r--r--packages/CredentialManager/res/values/strings.xml5
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt121
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt42
-rw-r--r--packages/SettingsLib/Android.bp1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt1
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt2
-rw-r--r--packages/SettingsLib/res/drawable/ic_external_display.xml28
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java115
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java12
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt53
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt35
-rw-r--r--packages/SettingsLib/tests/integ/Android.bp1
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt32
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt79
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java273
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java5
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java35
-rw-r--r--packages/Shell/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig7
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt7
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt47
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt39
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt197
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt32
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt23
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt25
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt194
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt117
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt10
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt110
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt8
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt44
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt66
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt83
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt132
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt189
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt175
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt202
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt)68
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt)93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt)26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt104
-rw-r--r--packages/SystemUI/res/layout/internet_connectivity_dialog.xml95
-rw-r--r--packages/SystemUI/res/layout/ongoing_privacy_chip.xml2
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml5
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/ids.xml1
-rw-r--r--packages/SystemUI/res/values/strings.xml4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java23
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java26
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java35
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java233
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt322
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java (renamed from packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeController.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt245
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt390
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt52
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt)8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt94
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt631
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt117
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt463
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java2
-rw-r--r--packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt42
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt59
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java)7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt24
-rw-r--r--ravenwood/framework-minus-apex-ravenwood-policies.txt12
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java9
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java36
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java175
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt29
-rw-r--r--services/accessibility/accessibility.aconfig7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java7
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteFillService.java95
-rw-r--r--services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java7
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java26
-rw-r--r--services/backup/flags.aconfig2
-rw-r--r--services/companion/TEST_MAPPING5
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java6
-rw-r--r--services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java7
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java55
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java5
-rw-r--r--services/core/Android.bp3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java23
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java5
-rw-r--r--services/core/java/com/android/server/am/AppBatteryExemptionTracker.java2
-rw-r--r--services/core/java/com/android/server/am/AppFGSTracker.java5
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java17
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java5
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java35
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java43
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java2
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java34
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java32
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java25
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java15
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java19
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java10
-rw-r--r--services/core/java/com/android/server/compat/CompatConfig.java12
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java6
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java19
-rw-r--r--services/core/java/com/android/server/input/InputSettingsObserver.java7
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java17
-rw-r--r--services/core/java/com/android/server/inputmethod/ClientController.java46
-rw-r--r--services/core/java/com/android/server/inputmethod/ClientState.java68
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java37
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java1
-rw-r--r--services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java2
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java24
-rw-r--r--services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java19
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java5
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java134
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java2
-rw-r--r--services/core/java/com/android/server/os/BugreportManagerServiceImpl.java11
-rw-r--r--services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS2
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/OWNERS2
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java7
-rw-r--r--services/core/java/com/android/server/pm/PreferredComponent.java17
-rw-r--r--services/core/java/com/android/server/pm/Settings.java2
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java29
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java3
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java5
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java6
-rw-r--r--services/core/java/com/android/server/power/feature/power_flags.aconfig2
-rw-r--r--services/core/java/com/android/server/selinux/QuotaLimiter.java78
-rw-r--r--services/core/java/com/android/server/selinux/RateLimiter.java85
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java155
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java144
-rw-r--r--services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java132
-rw-r--r--services/core/java/com/android/server/stats/Android.bp12
-rw-r--r--services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java285
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java59
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java31
-rw-r--r--services/core/java/com/android/server/stats/stats_flags.aconfig9
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java45
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java3
-rw-r--r--services/core/java/com/android/server/wearable/WearableSensingManagerService.java27
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java48
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java99
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java61
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java47
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java27
-rw-r--r--services/core/java/com/android/server/wm/DisplayFrames.java34
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java17
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java9
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java38
-rw-r--r--services/core/java/com/android/server/wm/Session.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java66
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java6
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java10
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimator.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java24
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java1
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java5
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp26
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java7
-rw-r--r--services/java/com/android/server/SystemServer.java18
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt18
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java43
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java275
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java67
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java18
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java113
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java202
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java644
-rw-r--r--services/tests/powerstatstests/Android.bp1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java57
-rw-r--r--services/tests/servicestests/Android.bp4
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java33
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt2
-rw-r--r--services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java32
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java102
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt24
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java137
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java62
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java126
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java72
-rw-r--r--telecomm/java/android/telecom/CallControl.java129
-rw-r--r--telecomm/java/android/telecom/PhoneAccount.java3
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java4
-rw-r--r--telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java3
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java24
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java98
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java151
-rw-r--r--telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl2
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt16
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java8
-rw-r--r--tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java35
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java167
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt14
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt15
692 files changed, 19585 insertions, 4535 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f5bf437a738d..98b62b3e2046 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -26,6 +26,7 @@ aconfig_srcjars = [
":android.content.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
+ ":android.crashrecovery.flags-aconfig-java{.generated_srcjars}",
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.database.sqlite-aconfig-java{.generated_srcjars}",
":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
@@ -64,7 +65,9 @@ aconfig_srcjars = [
":com.android.input.flags-aconfig-java{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+ ":com.android.media.flags.editing-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.net.thread.flags-aconfig-java{.generated_srcjars}",
":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
":com.android.text.flags-aconfig-java{.generated_srcjars}",
":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
@@ -92,6 +95,7 @@ stubs_defaults {
"android.companion.virtual.flags-aconfig",
"android.content.pm.flags-aconfig",
"android.content.res.flags-aconfig",
+ "android.crashrecovery.flags-aconfig",
"android.credentials.flags-aconfig",
"android.database.sqlite-aconfig",
"android.hardware.biometrics.flags-aconfig",
@@ -133,6 +137,7 @@ stubs_defaults {
"com.android.input.flags-aconfig",
"com.android.media.flags.bettertogether-aconfig",
"com.android.net.flags-aconfig",
+ "com.android.net.thread.flags-aconfig",
"com.android.server.flags.services-aconfig",
"com.android.text.flags-aconfig",
"com.android.window.flags.window-aconfig",
@@ -536,6 +541,21 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media Editing
+aconfig_declarations {
+ name: "com.android.media.flags.editing-aconfig",
+ package: "com.android.media.editing.flags",
+ srcs: [
+ "media/java/android/media/flags/editing.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "com.android.media.flags.editing-aconfig-java",
+ aconfig_declarations: "com.android.media.flags.editing-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media TV
aconfig_declarations {
name: "android.media.tv.flags-aconfig",
@@ -760,12 +780,25 @@ aconfig_declarations {
srcs: ["core/java/android/net/flags.aconfig"],
}
+// Thread network
+aconfig_declarations {
+ name: "com.android.net.thread.flags-aconfig",
+ package: "com.android.net.thread.flags",
+ srcs: ["core/java/android/net/thread/flags.aconfig"],
+}
+
java_aconfig_library {
name: "com.android.net.flags-aconfig-java",
aconfig_declarations: "com.android.net.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "com.android.net.thread.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.thread.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media
aconfig_declarations {
name: "android.media.playback.flags-aconfig",
@@ -1047,3 +1080,16 @@ java_aconfig_library {
aconfig_declarations: "android.adaptiveauth.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// CrashRecovery Module
+aconfig_declarations {
+ name: "android.crashrecovery.flags-aconfig",
+ package: "android.crashrecovery.flags",
+ srcs: ["packages/CrashRecovery/aconfig/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.crashrecovery.flags-aconfig-java",
+ aconfig_declarations: "android.crashrecovery.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
index 9c56733650cb..e12f74fcd7ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -95,6 +95,7 @@ filegroup {
":platform-compat-native-aidl",
// AIDL sources from external directories
+ ":android.frameworks.location.altitude-V2-java-source",
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V3-java-source",
":android.hardware.biometrics.face-V4-java-source",
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
index 0ea2dafbb047..bc8470888a5a 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/AbstractContentCapturePerfTestCase.java
@@ -248,6 +248,11 @@ public abstract class AbstractContentCapturePerfTestCase {
return mServiceWatcher.waitOnCreate();
}
+ /** Wait for session paused. */
+ public void waitForSessionPaused() throws InterruptedException {
+ mServiceWatcher.waitSessionPaused();
+ }
+
@NonNull
protected ActivityWatcher startWatcher() {
return mActivitiesWatcher.watch(CustomTestActivity.class);
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
index aa95dfdfdf16..44e8a67e72ef 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/LoginTest.java
@@ -80,6 +80,34 @@ public class LoginTest extends AbstractContentCapturePerfTestCase {
testActivityLaunchTime(R.layout.test_container_activity, 500);
}
+ @Test
+ public void testSendEventsLatency() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 0);
+ }
+
+ @Test
+ public void testSendEventsLatency_contains100Views() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 100);
+ }
+
+ @Test
+ public void testSendEventsLatency_contains300Views() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 300);
+ }
+
+ @Test
+ public void testSendEventsLatency_contains500Views() throws Throwable {
+ enableService();
+
+ testSendEventLatency(R.layout.test_container_activity, 500);
+ }
+
private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable {
final Object drawNotifier = new Object();
final Intent intent = getLaunchIntent(layoutId, numViews);
@@ -111,6 +139,38 @@ public class LoginTest extends AbstractContentCapturePerfTestCase {
}
}
+ private void testSendEventLatency(int layoutId, int numViews) throws Throwable {
+ final Object drawNotifier = new Object();
+ final Intent intent = getLaunchIntent(layoutId, numViews);
+ intent.putExtra(CustomTestActivity.INTENT_EXTRA_FINISH_ON_IDLE, true);
+ intent.putExtra(CustomTestActivity.INTENT_EXTRA_DRAW_CALLBACK,
+ new RemoteCallback(result -> {
+ synchronized (drawNotifier) {
+ drawNotifier.notifyAll();
+ }
+ }));
+ final ActivityWatcher watcher = startWatcher();
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ mEntryActivity.startActivity(intent);
+ synchronized (drawNotifier) {
+ try {
+ drawNotifier.wait(GENERIC_TIMEOUT_MS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ waitForSessionPaused();
+
+ // Ignore the time to finish the activity
+ state.pauseTiming();
+ watcher.waitFor(DESTROYED);
+ sInstrumentation.waitForIdleSync();
+ state.resumeTiming();
+ }
+ }
+
@Test
public void testOnVisibilityAggregated_visibleChanged() throws Throwable {
enableService();
diff --git a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
index ecc5112ab6dd..0b5345f5e2e1 100644
--- a/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
+++ b/apct-tests/perftests/contentcapture/src/android/view/contentcapture/MyContentCaptureService.java
@@ -114,6 +114,10 @@ public class MyContentCaptureService extends ContentCaptureService {
public void onContentCaptureEvent(ContentCaptureSessionId sessionId,
ContentCaptureEvent event) {
Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event);
+ if (sServiceWatcher != null
+ && event.getType() == ContentCaptureEvent.TYPE_SESSION_PAUSED) {
+ sServiceWatcher.mSessionPaused.countDown();
+ }
}
@Override
@@ -126,6 +130,7 @@ public class MyContentCaptureService extends ContentCaptureService {
private static final long GENERIC_TIMEOUT_MS = 10_000;
private final CountDownLatch mCreated = new CountDownLatch(1);
private final CountDownLatch mDestroyed = new CountDownLatch(1);
+ private final CountDownLatch mSessionPaused = new CountDownLatch(1);
private boolean mReadyToClear = true;
private Pair<Set<String>, Set<ComponentName>> mAllowList;
@@ -151,6 +156,11 @@ public class MyContentCaptureService extends ContentCaptureService {
await(mDestroyed, "not destroyed");
}
+ /** Wait for session paused. */
+ public void waitSessionPaused() throws InterruptedException {
+ await(mSessionPaused, "no Paused");
+ }
+
/**
* Allow just this package.
*/
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index e73b434042af..788e82407926 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -13,3 +13,10 @@ flag {
description: "Add APIs to let apps attach debug information to jobs"
bug: "293491637"
}
+
+flag {
+ name: "backup_jobs_exemption"
+ namespace: "backstage_power"
+ description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content."
+ bug: "318731461"
+}
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index de6f0235cd83..5d65d9d0629f 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -1,6 +1,13 @@
package: "com.android.server.job"
flag {
+ name: "do_not_force_rush_execution_at_boot"
+ namespace: "backstage_power"
+ description: "Don't force rush job execution right after boot completion"
+ bug: "321598070"
+}
+
+flag {
name: "relax_prefetch_connectivity_constraint_only_on_charger"
namespace: "backstage_power"
description: "Only relax a prefetch job's connectivity constraint when the device is charging and battery is not low"
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 31214cbb7066..696c3178a4f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -3919,6 +3919,7 @@ public class DeviceIdleController extends SystemService
if (locationManager != null
&& locationManager.getProvider(LocationManager.FUSED_PROVIDER)
!= null) {
+ mHasFusedLocation = true;
locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
mLocationRequest,
AppSchedulingModuleThread.getExecutor(),
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index b0f378d1752d..fc193d8147b5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1840,7 +1840,9 @@ public class JobSchedulerService extends com.android.server.SystemService
/* isFlexConstraintSatisfied */ false,
jobStatus.canApplyTransportAffinities(),
jobStatus.getNumAppliedFlexibleConstraints(),
- jobStatus.getNumDroppedFlexibleConstraints());
+ jobStatus.getNumDroppedFlexibleConstraints(),
+ jobStatus.getFilteredTraceTag(),
+ jobStatus.getFilteredDebugTags());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2288,7 +2290,9 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
cancelled.canApplyTransportAffinities(),
cancelled.getNumAppliedFlexibleConstraints(),
- cancelled.getNumDroppedFlexibleConstraints());
+ cancelled.getNumDroppedFlexibleConstraints(),
+ cancelled.getFilteredTraceTag(),
+ cancelled.getFilteredDebugTags());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
@@ -2720,8 +2724,10 @@ public class JobSchedulerService extends com.android.server.SystemService
sc.maybeStartTrackingJobLocked(job, null);
}
});
- // GO GO GO!
- mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ if (!Flags.doNotForceRushExecutionAtBoot()) {
+ // GO GO GO!
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
}
}
}
@@ -5441,9 +5447,14 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.println("Aconfig flags:");
pw.increaseIndent();
+ pw.print(Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT,
+ Flags.doNotForceRushExecutionAtBoot());
pw.print(Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE,
Flags.throwOnUnsupportedBiasUsage());
pw.println();
+ pw.print(android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION,
+ android.app.job.Flags.backupJobsExemption());
+ pw.println();
pw.decreaseIndent();
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index c14efae3fa62..0cf6a7a8a4f6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -344,15 +344,21 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
final String flagName = getNextArgRequired();
switch (flagName) {
+ case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
+ pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
+ break;
case android.app.job.Flags.FLAG_JOB_DEBUG_INFO_APIS:
pw.println(android.app.job.Flags.jobDebugInfoApis());
break;
- case android.app.job.Flags.FLAG_ENFORCE_MINIMUM_TIME_WINDOWS:
- pw.println(android.app.job.Flags.enforceMinimumTimeWindows());
+ case com.android.server.job.Flags.FLAG_DO_NOT_FORCE_RUSH_EXECUTION_AT_BOOT:
+ pw.println(com.android.server.job.Flags.doNotForceRushExecutionAtBoot());
break;
case com.android.server.job.Flags.FLAG_THROW_ON_UNSUPPORTED_BIAS_USAGE:
pw.println(com.android.server.job.Flags.throwOnUnsupportedBiasUsage());
break;
+ case android.app.job.Flags.FLAG_BACKUP_JOBS_EXEMPTION:
+ pw.println(android.app.job.Flags.backupJobsExemption());
+ break;
default:
pw.println("Unknown flag: " + flagName);
break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index fe55e2702916..8ab7d2fae49f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -541,7 +541,9 @@ public final class JobServiceContext implements ServiceConnection {
job.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
job.canApplyTransportAffinities(),
job.getNumAppliedFlexibleConstraints(),
- job.getNumDroppedFlexibleConstraints());
+ job.getNumDroppedFlexibleConstraints(),
+ job.getFilteredTraceTag(),
+ job.getFilteredDebugTags());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1630,7 +1632,9 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.isConstraintSatisfied(JobStatus.CONSTRAINT_FLEXIBLE),
completedJob.canApplyTransportAffinities(),
completedJob.getNumAppliedFlexibleConstraints(),
- completedJob.getNumDroppedFlexibleConstraints());
+ completedJob.getNumDroppedFlexibleConstraints(),
+ completedJob.getFilteredTraceTag(),
+ completedJob.getFilteredDebugTags());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 14cce19ef676..6883d18cd937 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -972,6 +972,20 @@ public final class FlexibilityController extends StateController {
synchronized (mLock) {
final long earliest = getLifeCycleBeginningElapsedLocked(js);
final long latest = getLifeCycleEndElapsedLocked(js, nowElapsed, earliest);
+ if (latest <= earliest) {
+ // Something has gone horribly wrong. This has only occurred on incorrectly
+ // configured tests, but add a check here for safety.
+ Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
+ + " Prefetch=" + js.getJob().isPrefetch());
+ // Since things have gone wrong, the safest and most reliable thing to do is
+ // stop applying flex policy to the job.
+ mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
+ js.getNumAppliedFlexibleConstraints());
+ mJobsToCheck.add(js);
+ mHandler.sendEmptyMessage(MSG_CHECK_JOBS);
+ return;
+ }
+
final long nextTimeElapsed =
getNextConstraintDropTimeElapsedLocked(js, earliest, latest);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index d39863c85f33..a4df5d829281 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -48,6 +48,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
+import android.util.Patterns;
import android.util.Range;
import android.util.Slog;
import android.util.TimeUtils;
@@ -76,6 +77,7 @@ import java.util.Collections;
import java.util.Objects;
import java.util.Random;
import java.util.function.Predicate;
+import java.util.regex.Pattern;
/**
* Uniquely identifies a job internally.
@@ -203,6 +205,17 @@ public final class JobStatus {
// TODO(b/129954980): ensure this doesn't spam statsd, especially at boot
private static final boolean STATS_LOG_ENABLED = false;
+ /**
+ * Simple patterns to match some common forms of PII. This is not intended all-encompassing and
+ * any clients should aim to do additional filtering.
+ */
+ private static final ArrayMap<Pattern, String> BASIC_PII_FILTERS = new ArrayMap<>();
+
+ static {
+ BASIC_PII_FILTERS.put(Patterns.EMAIL_ADDRESS, "[EMAIL]");
+ BASIC_PII_FILTERS.put(Patterns.PHONE, "[PHONE]");
+ }
+
// No override.
public static final int OVERRIDE_NONE = 0;
// Override to improve sorting order. Does not affect constraint evaluation.
@@ -250,6 +263,18 @@ public final class JobStatus {
private final long mLoggingJobId;
/**
+ * List of tags from {@link JobInfo#getDebugTags()}, filtered using {@link #BASIC_PII_FILTERS}.
+ * Lazily loaded in {@link #getFilteredDebugTags()}.
+ */
+ @Nullable
+ private String[] mFilteredDebugTags;
+ /**
+ * Trace tag from {@link JobInfo#getTraceTag()}, filtered using {@link #BASIC_PII_FILTERS}.
+ * Lazily loaded in {@link #getFilteredTraceTag()}.
+ */
+ @Nullable
+ private String mFilteredTraceTag;
+ /**
* Tag to identify the wakelock held for this job. Lazily loaded in
* {@link #getWakelockTag()} since it's not typically needed until the job is about to run.
*/
@@ -1325,6 +1350,47 @@ public final class JobStatus {
return batteryName;
}
+ @VisibleForTesting
+ @NonNull
+ static String applyBasicPiiFilters(@NonNull String val) {
+ for (int i = BASIC_PII_FILTERS.size() - 1; i >= 0; --i) {
+ val = BASIC_PII_FILTERS.keyAt(i).matcher(val).replaceAll(BASIC_PII_FILTERS.valueAt(i));
+ }
+ return val;
+ }
+
+ /**
+ * List of tags from {@link JobInfo#getDebugTags()}, filtered using a basic set of PII filters.
+ */
+ @NonNull
+ public String[] getFilteredDebugTags() {
+ if (mFilteredDebugTags != null) {
+ return mFilteredDebugTags;
+ }
+ final ArraySet<String> debugTags = job.getDebugTagsArraySet();
+ mFilteredDebugTags = new String[debugTags.size()];
+ for (int i = 0; i < mFilteredDebugTags.length; ++i) {
+ mFilteredDebugTags[i] = applyBasicPiiFilters(debugTags.valueAt(i));
+ }
+ return mFilteredDebugTags;
+ }
+
+ /**
+ * Trace tag from {@link JobInfo#getTraceTag()}, filtered using a basic set of PII filters.
+ */
+ @Nullable
+ public String getFilteredTraceTag() {
+ if (mFilteredTraceTag != null) {
+ return mFilteredTraceTag;
+ }
+ final String rawTag = job.getTraceTag();
+ if (rawTag == null) {
+ return null;
+ }
+ mFilteredTraceTag = applyBasicPiiFilters(rawTag);
+ return mFilteredTraceTag;
+ }
+
/** Return the String to be used as the tag for the wakelock held for this job. */
@NonNull
public String getWakelockTag() {
diff --git a/core/api/Android.bp b/core/api/Android.bp
index 907916a125da..8d8a82b69b55 100644
--- a/core/api/Android.bp
+++ b/core/api/Android.bp
@@ -96,21 +96,3 @@ 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 6f898378c563..66feebc6cc92 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -277,6 +277,7 @@ package android {
field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+ field @FlaggedApi("android.app.job.backup_jobs_exemption") public static final String RUN_BACKUP_JOBS = "android.permission.RUN_BACKUP_JOBS";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
@@ -284,6 +285,7 @@ package android {
field public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
field public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
field public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
+ field @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public static final String SET_BIOMETRIC_DIALOG_LOGO = "android.permission.SET_BIOMETRIC_DIALOG_LOGO";
field public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
field @Deprecated public static final String SET_PREFERRED_APPLICATIONS = "android.permission.SET_PREFERRED_APPLICATIONS";
field public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
@@ -7711,7 +7713,7 @@ package android.app {
method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int);
method public int getWallpaperId(int);
- method public android.app.WallpaperInfo getWallpaperInfo();
+ method @RequiresPermission(value="QUERY_ALL_PACKAGES", conditional=true) public android.app.WallpaperInfo getWallpaperInfo();
method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int);
method public boolean hasResourceWallpaper(@RawRes int);
method public boolean isSetWallpaperAllowed();
@@ -18757,8 +18759,8 @@ package android.hardware.biometrics {
method @Nullable public int getAllowedAuthenticators();
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
method @Nullable public CharSequence getDescription();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap();
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes();
method @Nullable public CharSequence getNegativeButtonText();
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
@@ -18808,8 +18810,8 @@ package android.hardware.biometrics {
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int);
- method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -18835,14 +18837,14 @@ package android.hardware.biometrics {
}
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
- ctor public PromptContentItemBulletedText(@NonNull CharSequence);
+ ctor public PromptContentItemBulletedText(@NonNull String);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
}
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
- ctor public PromptContentItemPlainText(@NonNull CharSequence);
+ ctor public PromptContentItemPlainText(@NonNull String);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
@@ -18853,7 +18855,7 @@ package android.hardware.biometrics {
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
method public int describeContents();
- method @Nullable public CharSequence getDescription();
+ method @Nullable public String getDescription();
method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
method public static int getMaxEachItemCharacterNumber();
method public static int getMaxItemCount();
@@ -18866,7 +18868,7 @@ package android.hardware.biometrics {
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem);
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int);
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
- method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence);
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull String);
}
}
@@ -22231,11 +22233,10 @@ package android.media {
@FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecController implements java.lang.AutoCloseable {
method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
- method public void close();
+ method @FlaggedApi("android.media.audio.loudness_configurator_api") public void close();
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecController create(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecController.OnLoudnessCodecUpdateListener);
method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.MediaCodec);
- method @FlaggedApi("android.media.audio.loudness_configurator_api") public void release();
method @FlaggedApi("android.media.audio.loudness_configurator_api") public void removeMediaCodec(@NonNull android.media.MediaCodec);
}
@@ -24335,7 +24336,7 @@ package android.media {
method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
- method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String, @NonNull android.os.UserHandle);
+ method @FlaggedApi("com.android.media.flags.enable_cross_user_routing_in_media_router2") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MEDIA_CONTENT_CONTROL, android.Manifest.permission.MEDIA_ROUTING_CONTROL}) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
method @FlaggedApi("com.android.media.flags.enable_rlp_callbacks_in_media_router2") @Nullable public android.media.RouteListingPreference getRouteListingPreference();
method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
@@ -25751,9 +25752,48 @@ package android.media.metrics {
field public static final String KEY_STATSD_ATOM = "bundlesession-statsd-atom";
}
+ @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public final class EditingEndedEvent extends android.media.metrics.Event implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getErrorCode();
+ method public int getFinalState();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.EditingEndedEvent> CREATOR;
+ field public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18; // 0x12
+ field public static final int ERROR_CODE_DECODER_INIT_FAILED = 11; // 0xb
+ field public static final int ERROR_CODE_DECODING_FAILED = 12; // 0xc
+ field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13; // 0xd
+ field public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14; // 0xe
+ field public static final int ERROR_CODE_ENCODING_FAILED = 15; // 0xf
+ field public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16; // 0x10
+ field public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2; // 0x2
+ field public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6; // 0x6
+ field public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9; // 0x9
+ field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7; // 0x7
+ field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4; // 0x4
+ field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5; // 0x5
+ field public static final int ERROR_CODE_IO_NO_PERMISSION = 8; // 0x8
+ field public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10; // 0xa
+ field public static final int ERROR_CODE_IO_UNSPECIFIED = 3; // 0x3
+ field public static final int ERROR_CODE_MUXING_FAILED = 19; // 0x13
+ field public static final int ERROR_CODE_NONE = 1; // 0x1
+ field public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17; // 0x11
+ field public static final int FINAL_STATE_CANCELED = 2; // 0x2
+ field public static final int FINAL_STATE_ERROR = 3; // 0x3
+ field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+ }
+
+ @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
+ ctor public EditingEndedEvent.Builder(int);
+ method @NonNull public android.media.metrics.EditingEndedEvent build();
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
+ method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
+ }
+
public final class EditingSession implements java.lang.AutoCloseable {
method public void close();
method @NonNull public android.media.metrics.LogSessionId getSessionId();
+ method @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public void reportEditingEndedEvent(@NonNull android.media.metrics.EditingEndedEvent);
}
public abstract class Event {
@@ -33465,6 +33505,7 @@ package android.os {
field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
field public static final String DISALLOW_SMS = "no_sms";
field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
+ field @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled") public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
field public static final String DISALLOW_ULTRA_WIDEBAND_RADIO = "no_ultra_wideband_radio";
field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
field public static final String DISALLOW_UNINSTALL_APPS = "no_uninstall_apps";
@@ -40611,7 +40652,7 @@ package android.service.notification {
method public int getPriorityCategoryReminders();
method public int getPriorityCategoryRepeatCallers();
method public int getPriorityCategorySystem();
- method @FlaggedApi("android.app.modes_api") public int getPriorityChannels();
+ method @FlaggedApi("android.app.modes_api") public int getPriorityChannelsAllowed();
method public int getPriorityConversationSenders();
method public int getPriorityMessageSenders();
method public int getVisualEffectAmbient();
@@ -41739,10 +41780,10 @@ package android.telecom {
method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method @NonNull public android.os.ParcelUuid getCallId();
method public void requestCallEndpointChange(@NonNull android.telecom.CallEndpoint, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+ method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void requestMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void sendEvent(@NonNull String, @NonNull android.os.Bundle);
method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
- method @FlaggedApi("com.android.server.telecom.flags.set_mute_state") public void setMuteState(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
}
@@ -53661,13 +53702,13 @@ package android.view {
method @Deprecated public android.view.Display getDefaultDisplay();
method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics();
method public default boolean isCrossWindowBlurEnabled();
- method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerBatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.view.Choreographer, @NonNull android.view.SurfaceControlInputReceiver);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @NonNull public default android.os.IBinder registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void registerUnbatchedSurfaceControlInputReceiver(int, @NonNull android.os.IBinder, @NonNull android.view.SurfaceControl, @NonNull android.os.Looper, @NonNull android.view.SurfaceControlInputReceiver);
method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer);
method public void removeViewImmediate(android.view.View);
- method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.os.IBinder);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") public default void unregisterSurfaceControlInputReceiver(@NonNull android.view.SurfaceControl);
method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>);
field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE";
field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0b17e03c147d..4c16af00a896 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -22,7 +22,7 @@ package android {
field public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE = "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
field public static final String ACCESS_SHARED_LIBRARIES = "android.permission.ACCESS_SHARED_LIBRARIES";
field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
- field public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
+ field @FlaggedApi("android.app.smartspace.flags.access_smartspace") public static final String ACCESS_SMARTSPACE = "android.permission.ACCESS_SMARTSPACE";
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
field public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
field public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
@@ -129,6 +129,7 @@ package android {
field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
+ field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
@@ -871,6 +872,10 @@ package android.app {
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR;
}
+ @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager {
+ method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long);
+ }
+
public class BroadcastOptions {
method public void clearRequireCompatChange();
method public int getPendingIntentBackgroundActivityStartMode();
@@ -3388,11 +3393,10 @@ package android.companion.virtual.camera {
}
@FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final class VirtualCameraConfig.Builder {
- ctor public VirtualCameraConfig.Builder();
+ ctor public VirtualCameraConfig.Builder(@NonNull String);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder addStreamConfig(@IntRange(from=1) int, @IntRange(from=1) int, int, @IntRange(from=1) int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig build();
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setLensFacing(int);
- method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setSensorOrientation(int);
method @NonNull public android.companion.virtual.camera.VirtualCameraConfig.Builder setVirtualCameraCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.camera.VirtualCameraCallback);
}
@@ -6886,7 +6890,6 @@ package android.media {
public final class MediaRouter2 {
method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
method @Nullable public String getClientPackageName();
- method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void startScan();
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void stopScan();
@@ -14774,6 +14777,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String);
method public boolean needsOtaServiceProvisioning();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.DUMP) public void persistEmergencyCallDiagnosticData(@NonNull String, @NonNull android.telephony.TelephonyManager.EmergencyCallDiagnosticParams);
method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerCarrierPrivilegesCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CarrierPrivilegesCallback);
@@ -14952,6 +14956,21 @@ package android.telephony {
method public default void onCarrierServiceChanged(@Nullable String, int);
}
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class TelephonyManager.EmergencyCallDiagnosticParams {
+ method public long getLogcatCollectionStartTimeMillis();
+ method public boolean isLogcatCollectionEnabled();
+ method public boolean isTelecomDumpSysCollectionEnabled();
+ method public boolean isTelephonyDumpSysCollectionEnabled();
+ }
+
+ public static final class TelephonyManager.EmergencyCallDiagnosticParams.Builder {
+ ctor public TelephonyManager.EmergencyCallDiagnosticParams.Builder();
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams build();
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setLogcatCollectionStartTimeMillis(long);
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelecomDumpSysCollectionEnabled(boolean);
+ method @NonNull public android.telephony.TelephonyManager.EmergencyCallDiagnosticParams.Builder setTelephonyDumpSysCollectionEnabled(boolean);
+ }
+
public static class TelephonyManager.ModemActivityInfoException extends java.lang.Exception {
ctor public TelephonyManager.ModemActivityInfoException(int);
method public int getErrorCode();
@@ -17159,7 +17178,7 @@ package android.telephony.satellite {
method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException;
+ method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteModemStateCallback);
@@ -17226,6 +17245,7 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b8b98a3cb3ba..7a3d320dddab 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3646,6 +3646,7 @@ package android.view {
public interface WindowManager extends android.view.ViewManager {
method public default int getDisplayImePolicy(int);
+ method @FlaggedApi("com.android.window.flags.surface_control_input_receiver") @Nullable public default android.os.IBinder getSurfaceControlInputClientToken(@NonNull android.view.SurfaceControl);
method public static boolean hasWindowExtensionsEnabled();
method public default void holdLock(android.os.IBinder, int);
method public default boolean isGlobalKey(int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index e288b42f7ec7..1bdbd4c50634 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6792,6 +6792,7 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
if (killApp) {
+ // Keep in sync with "perhaps it was removed" case below.
mPackages.remove(packages[i]);
mResourcePackages.remove(packages[i]);
}
@@ -6859,6 +6860,12 @@ public final class ActivityThread extends ClientTransactionHandler
}
} catch (RemoteException e) {
}
+ } else {
+ // No package, perhaps it was removed?
+ Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+ + " but missing application info. Assuming REMOVED.");
+ mPackages.remove(packages[i]);
+ mResourcePackages.remove(packages[i]);
}
}
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d8d136ae4df9..00c4b0f6515f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1548,9 +1548,16 @@ public class AppOpsManager {
public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+ /**
+ * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ *
+ * @hide
+ */
+ public static final int OP_RUN_BACKUP_JOBS = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 144;
+ public static final int _NUM_OP = 145;
/**
* All app ops represented as strings.
@@ -1700,6 +1707,7 @@ public class AppOpsManager {
OPSTR_ENABLE_MOBILE_DATA_BY_USER,
OPSTR_RESERVED_FOR_TESTING,
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
+ OPSTR_RUN_BACKUP_JOBS,
})
public @interface AppOpString {}
@@ -2392,6 +2400,13 @@ public class AppOpsManager {
public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
"android:read_system_grammatical_gender";
+ /**
+ * Allows an app whose primary use case is to backup or sync content to run longer jobs.
+ *
+ * @hide
+ */
+ public static final String OPSTR_RUN_BACKUP_JOBS = "android:run_backup_jobs";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2504,6 +2519,7 @@ public class AppOpsManager {
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OP_RUN_BACKUP_JOBS,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2958,8 +2974,11 @@ public class AppOpsManager {
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
- .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+ // will make it an app-op permission in the future.
+ // .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
.build(),
+ new AppOpInfo.Builder(OP_RUN_BACKUP_JOBS, OPSTR_RUN_BACKUP_JOBS, "RUN_BACKUP_JOBS")
+ .setPermission(Manifest.permission.RUN_BACKUP_JOBS).build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java
new file mode 100644
index 000000000000..664fcebcfc05
--- /dev/null
+++ b/core/java/android/app/BackgroundInstallControlManager.java
@@ -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 android.app;
+
+import static android.Manifest.permission.GET_BACKGROUND_INSTALLED_PACKAGES;
+import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.util.List;
+
+/**
+ * BackgroundInstallControlManager client allows apps to query apps installed in background.
+ *
+ * <p>Any applications that was installed without an accompanying installer UI activity paired
+ * with recorded user interaction event is considered background installed. This is determined by
+ * analysis of user-activity logs.
+ *
+ * <p>Warning: BackgroundInstallControl should not be considered a definitive
+ * authority of identifying background installed applications. Consumers can use this as a
+ * supplementary signal, but must perform additional due diligence to confirm the install nature
+ * of the package.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_BIC_CLIENT)
+@SystemApi(client = PRIVILEGED_APPS)
+@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)
+public final class BackgroundInstallControlManager {
+
+ private static final String TAG = "BackgroundInstallControlManager";
+ private static IBackgroundInstallControlService sService;
+ private final Context mContext;
+
+ BackgroundInstallControlManager(Context context) {
+ mContext = context;
+ }
+
+ private static IBackgroundInstallControlService getService() {
+ if (sService == null) {
+ sService =
+ IBackgroundInstallControlService.Stub.asInterface(
+ ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ }
+ return sService;
+ }
+
+ /**
+ * Returns a full list of {@link PackageInfo} of apps currently installed for the current user
+ * that are considered installed in the background.
+ *
+ * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on
+ * background-installed applications.
+ * <p>
+ *
+ * @param flags - Flags will be used to call
+ * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages.
+ * @return A list of packages retrieved from {@link PackageManager} with non-background
+ * installed app filter applied.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_BIC_CLIENT)
+ @SystemApi
+ @RequiresPermission(GET_BACKGROUND_INSTALLED_PACKAGES)
+ public @NonNull List<PackageInfo> getBackgroundInstalledPackages(
+ @PackageManager.PackageInfoFlagsBits long flags) {
+ List<PackageInfo> backgroundInstalledPackages;
+ try {
+ return getService()
+ .getBackgroundInstalledPackages(flags, mContext.getUserId())
+ .getList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+}
diff --git a/core/java/android/app/HomeVisibilityListener.java b/core/java/android/app/HomeVisibilityListener.java
index 1f5f2e4c8237..5dd7ab0f99fa 100644
--- a/core/java/android/app/HomeVisibilityListener.java
+++ b/core/java/android/app/HomeVisibilityListener.java
@@ -69,6 +69,11 @@ public abstract class HomeVisibilityListener {
public HomeVisibilityListener() {
mObserver = new android.app.IProcessObserver.Stub() {
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid,
+ String packageName, String processName) {
+ }
+
+ @Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
refreshHomeVisibility();
}
diff --git a/core/java/android/app/IProcessObserver.aidl b/core/java/android/app/IProcessObserver.aidl
index 7be3620f317b..5c5e72cf9d6f 100644
--- a/core/java/android/app/IProcessObserver.aidl
+++ b/core/java/android/app/IProcessObserver.aidl
@@ -18,6 +18,17 @@ package android.app;
/** {@hide} */
oneway interface IProcessObserver {
+ /**
+ * Invoked when an app process starts up.
+ *
+ * @param pid The pid of the process.
+ * @param processUid The UID associated with the process.
+ * @param packageUid The UID associated with the package.
+ * @param packageName The name of the package.
+ * @param processName The name of the process.
+ */
+ void onProcessStarted(int pid, int processUid, int packageUid,
+ @utf8InCpp String packageName, @utf8InCpp String processName);
void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities);
void onForegroundServicesChanged(int pid, int uid, int serviceTypes);
void onProcessDied(int pid, int uid);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ed0cfbe3d9c3..a81ad3c429ea 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5487,6 +5487,15 @@ public class Notification implements Parcelable
return mColors;
}
+ /**
+ * @param isHeader If the notification is a notification header
+ * @return An instance of mColors after resolving the palette
+ */
+ private Colors getColors(boolean isHeader) {
+ mColors.resolvePalette(mContext, mN.color, !isHeader && mN.isColorized(), mInNightMode);
+ return mColors;
+ }
+
private void updateBackgroundColor(RemoteViews contentView,
StandardTemplateParams p) {
if (isBackgroundColorized(p)) {
@@ -6618,6 +6627,23 @@ public class Notification implements Parcelable
return getColors(p).getContrastColor();
}
+ /**
+ * Gets the foreground color of the small icon. If the notification is colorized, this
+ * is the primary text color, otherwise it's the contrast-adjusted app-provided color.
+ * @hide
+ */
+ public @ColorInt int getSmallIconColor(boolean isHeader) {
+ return getColors(/* isHeader = */ isHeader).getContrastColor();
+ }
+
+ /**
+ * Gets the background color of the notification.
+ * @hide
+ */
+ public @ColorInt int getBackgroundColor(boolean isHeader) {
+ return getColors(/* isHeader = */ isHeader).getBackgroundColor();
+ }
+
/** @return the theme's accent color for colored UI elements. */
private @ColorInt int getPrimaryAccentColor(StandardTemplateParams p) {
return getColors(p).getPrimaryAccentColor();
@@ -8532,6 +8558,8 @@ public class Notification implements Parcelable
boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
boolean isHeaderless = !isConversationLayout && isCollapsed;
+ //TODO (b/217799515): ensure mConversationTitle always returns the correct
+ // conversationTitle, probably set mConversationTitle = conversationTitle after this
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 0760d4db9169..3b5bba20a10b 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -90,8 +90,8 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve
per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
# BackgroundInstallControlManager
-per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
-per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
# ResourcesManager
per-file ResourcesManager.java = file:RESOURCES_OWNERS
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 36b03c1b1f48..0116ca24ec97 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1899,15 +1899,22 @@ public class WallpaperManager {
/**
* Returns the information about the home screen wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image, this returns null.
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+ * caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
- * In order to use this, apps should declare a {@code <queries>} tag with the action
- * {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
+ * Before Android U, this method requires the
+ * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
+ * </p>
+ *
+ * <p>
+ * Starting from Android U, in order to use this, apps should declare a {@code <queries>} tag
+ * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
* this method will return {@code null} if the caller doesn't otherwise have
* <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
* </p>
*/
+ @RequiresPermission(value = "QUERY_ALL_PACKAGES", conditional = true)
public WallpaperInfo getWallpaperInfo() {
return getWallpaperInfoForUser(mContext.getUserId());
}
@@ -1924,19 +1931,14 @@ public class WallpaperManager {
}
/**
- * Returns the information about the home screen wallpaper if its current wallpaper is a live
- * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
+ * Returns the information about the designated wallpaper if its current wallpaper is a live
+ * wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if
* the caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
- * Before Android U, this method requires the
- * {@link android.Manifest.permission#QUERY_ALL_PACKAGES} permission.
- * </p>
- *
- * <p>
- * Starting from Android U, In order to use this, apps should declare a {@code <queries>} tag
- * with the action {@code "android.service.wallpaper.WallpaperService"}. Otherwise,
- * this method will return {@code null} if the caller doesn't otherwise have
+ * In order to use this, apps should declare a {@code <queries>} tag with the action
+ * {@code "android.service.wallpaper.WallpaperService"}. Otherwise, this method will return
+ * {@code null} if the caller doesn't otherwise have
* <a href="{@docRoot}training/package-visibility">visibility</a> of the wallpaper package.
* </p>
*
@@ -1952,7 +1954,7 @@ public class WallpaperManager {
/**
* Returns the information about the designated wallpaper if its current wallpaper is a live
* wallpaper component. Otherwise, if the wallpaper is a static image or is not set, or if the
- * the caller doesn't have the appropriate permissions, this returns {@code null}.
+ * caller doesn't have the appropriate permissions, this returns {@code null}.
*
* <p>
* In order to use this, apps should declare a {@code <queries>} tag
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 325aa28fde08..83e18ec05599 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -133,4 +133,10 @@ interface IVirtualDeviceManager {
* device.
*/
boolean isVirtualDeviceOwnedMirrorDisplay(int displayId);
+
+ /**
+ * Returns all current persistent device IDs, including the ones for which no virtual device
+ * exists, as long as one may have existed or can be created.
+ */
+ List<String> getAllPersistentDeviceIds();
}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
index 350cf3d832d6..06a0f5c09e18 100644
--- a/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
+++ b/core/java/android/companion/virtual/camera/VirtualCameraConfig.java
@@ -196,13 +196,12 @@ public final class VirtualCameraConfig implements Parcelable {
* <li>At least one stream must be added with {@link #addStreamConfig(int, int, int, int)}.
* <li>A callback must be set with {@link #setVirtualCameraCallback(Executor,
* VirtualCameraCallback)}
- * <li>A camera name must be set with {@link #setName(String)}
* <li>A lens facing must be set with {@link #setLensFacing(int)}
*/
@FlaggedApi(Flags.FLAG_VIRTUAL_CAMERA)
public static final class Builder {
- private String mName;
+ private final String mName;
private final ArraySet<VirtualCameraStreamConfig> mStreamConfigurations = new ArraySet<>();
private Executor mCallbackExecutor;
private VirtualCameraCallback mCallback;
@@ -210,12 +209,12 @@ public final class VirtualCameraConfig implements Parcelable {
private int mLensFacing = LENS_FACING_UNKNOWN;
/**
- * Sets the name of the virtual camera instance.
+ * Creates a new instance of {@link Builder}.
+ *
+ * @param name The name of the {@link VirtualCamera}.
*/
- @NonNull
- public Builder setName(@NonNull String name) {
- mName = requireNonNull(name, "Display name cannot be null");
- return this;
+ public Builder(@NonNull String name) {
+ mName = requireNonNull(name, "Name cannot be null");
}
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4724e866f094..8744eaee4341 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -128,6 +128,7 @@ import java.util.function.Function;
* <a href="/training/basics/intents/package-visibility">manage package visibility</a>.
* </p>
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public abstract class PackageManager {
private static final String TAG = "PackageManager";
@@ -5492,6 +5493,7 @@ public abstract class PackageManager {
* application info.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static class Flags {
final long mValue;
protected Flags(long value) {
@@ -5506,6 +5508,7 @@ public abstract class PackageManager {
* Specific flags used for retrieving package info. Example:
* {@code PackageManager.getPackageInfo(packageName, PackageInfoFlags.of(0)}
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class PackageInfoFlags extends Flags {
private PackageInfoFlags(@PackageInfoFlagsBits long value) {
super(value);
@@ -5519,6 +5522,7 @@ public abstract class PackageManager {
/**
* Specific flags used for retrieving application info.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class ApplicationInfoFlags extends Flags {
private ApplicationInfoFlags(@ApplicationInfoFlagsBits long value) {
super(value);
@@ -5532,6 +5536,7 @@ public abstract class PackageManager {
/**
* Specific flags used for retrieving component info.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class ComponentInfoFlags extends Flags {
private ComponentInfoFlags(@ComponentInfoFlagsBits long value) {
super(value);
@@ -5545,6 +5550,7 @@ public abstract class PackageManager {
/**
* Specific flags used for retrieving resolve info.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public final static class ResolveInfoFlags extends Flags {
private ResolveInfoFlags(@ResolveInfoFlagsBits long value) {
super(value);
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 8fd78bd4276c..3e9f260566bd 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -52,6 +52,7 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
@TestApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class UserInfo implements Parcelable {
/**
@@ -438,6 +439,7 @@ public class UserInfo implements Parcelable {
/**
* @return true if this user can be switched to.
**/
+ @android.ravenwood.annotation.RavenwoodThrow
public boolean supportsSwitchTo() {
if (partial || !isEnabled()) {
// Don't support switching to disabled or partial users, which includes users with
@@ -455,6 +457,7 @@ public class UserInfo implements Parcelable {
* @return true if user is of type {@link UserManager#USER_TYPE_SYSTEM_HEADLESS} and
* {@link com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser} is true.
*/
+ @android.ravenwood.annotation.RavenwoodThrow
private boolean canSwitchToHeadlessSystemUser() {
return UserManager.USER_TYPE_SYSTEM_HEADLESS.equals(userType) && Resources.getSystem()
.getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
@@ -465,6 +468,7 @@ public class UserInfo implements Parcelable {
* @deprecated Use {@link UserInfo#supportsSwitchTo} instead.
*/
@Deprecated
+ @android.ravenwood.annotation.RavenwoodThrow
public boolean supportsSwitchToByUser() {
return supportsSwitchTo();
}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index cbf5274c4fa1..caff4576c9c3 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -161,3 +161,25 @@ flag {
bug: "291135724"
is_fixed_read_only: true
}
+
+flag {
+ name: "fix_system_apps_first_install_time"
+ namespace: "package_manager_service"
+ description: "Feature flag to fix the first-install timestamps for system apps."
+ bug: "321258605"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "allow_sdk_sandbox_query_intent_activities"
+ namespace: "package_manager_service"
+ description: "Feature flag to allow the sandbox SDK to query intent activities of the client app."
+ bug: "295842134"
+}
+
+flag {
+ name: "emergency_install_permission"
+ namespace: "permissions"
+ description: "Feature flag to enable permission EMERGENCY_INSTALL_PACKAGES"
+ bug: "321080601"
+}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 9644d8095a4d..c08343713abb 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -85,6 +85,7 @@ flag {
description: "Enable auto-locking private space on device restarts"
bug: "296993385"
}
+
flag {
name: "enable_system_user_only_for_services_and_providers"
namespace: "multiuser"
@@ -92,3 +93,10 @@ flag {
bug: "302354856"
is_fixed_read_only: true
}
+
+flag {
+ name: "allow_private_profile_apis"
+ namespace: "profile_experiences"
+ description: "Enable only the API changes to support private space"
+ bug: "299069460"
+}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index cbe4c62d7069..625d7cb66900 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -58,6 +58,16 @@ public class FontScaleConverterFactory {
synchronized (LOOKUP_TABLES_WRITE_LOCK) {
putInto(
sLookupTables,
+ /* scaleKey= */ 1.1f,
+ new FontScaleConverterImpl(
+ /* fromSp= */
+ new float[] { 8f, 10f, 12f, 14f, 18f, 20f, 24f, 30f, 100},
+ /* toDp= */
+ new float[] { 8.8f, 11f, 13.2f, 15.6f, 19.2f, 21.2f, 24.8f, 30f, 100})
+ );
+
+ putInto(
+ sLookupTables,
/* scaleKey= */ 1.15f,
new FontScaleConverterImpl(
/* fromSp= */
diff --git a/core/java/android/credentials/ui/AuthenticationEntry.java b/core/java/android/credentials/ui/AuthenticationEntry.java
index b1a382c8ea96..9bd0871b3d3b 100644
--- a/core/java/android/credentials/ui/AuthenticationEntry.java
+++ b/core/java/android/credentials/ui/AuthenticationEntry.java
@@ -34,15 +34,24 @@ import java.lang.annotation.RetentionPolicy;
/**
* An authentication entry.
*
+ * Applicable only for credential retrieval flow, authentication entries are a special type of
+ * entries that require the user to unlock the given provider before its credential options can
+ * be fully rendered.
+ *
* @hide
*/
@TestApi
public final class AuthenticationEntry implements Parcelable {
- @NonNull private final String mKey;
- @NonNull private final String mSubkey;
- @NonNull private final @Status int mStatus;
- @Nullable private Intent mFrameworkExtrasIntent;
- @NonNull private final Slice mSlice;
+ @NonNull
+ private final String mKey;
+ @NonNull
+ private final String mSubkey;
+ @NonNull
+ private final @Status int mStatus;
+ @Nullable
+ private Intent mFrameworkExtrasIntent;
+ @NonNull
+ private final Slice mSlice;
/** @hide **/
@IntDef(prefix = {"STATUS_"}, value = {
@@ -51,15 +60,21 @@ public final class AuthenticationEntry implements Parcelable {
STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface Status {}
+ public @interface Status {
+ }
/** This entry is still locked, as initially supplied by the provider. */
public static final int STATUS_LOCKED = 0;
- /** This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
- * there is another such entry that was unlocked more recently. */
+ /**
+ * This entry was unlocked but didn't contain any credential. Meanwhile, "less recent" means
+ * there is another such entry that was unlocked more recently.
+ */
public static final int STATUS_UNLOCKED_BUT_EMPTY_LESS_RECENT = 1;
- /** This is the most recent entry that was unlocked but didn't contain any credential.
- * There should be at most one authentication entry with this status. */
+ /**
+ * This is the most recent entry that was unlocked but didn't contain any credential.
+ *
+ * There will be at most one authentication entry with this status.
+ */
public static final int STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT = 2;
private AuthenticationEntry(@NonNull Parcel in) {
@@ -74,9 +89,11 @@ public final class AuthenticationEntry implements Parcelable {
AnnotationValidations.validate(NonNull.class, null, mSlice);
}
- /** Constructor to be used for an entry that does not require further activities
+ /**
+ * Constructor to be used for an entry that does not require further activities
* to be invoked when selected.
*/
+ // TODO(b/322065508): remove this constructor.
public AuthenticationEntry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@Status int status) {
mKey = key;
@@ -95,9 +112,9 @@ public final class AuthenticationEntry implements Parcelable {
}
/**
- * Returns the identifier of this entry that's unique within the context of the CredentialManager
- * request.
- */
+ * Returns the identifier of this entry that's unique within the context of the
+ * CredentialManager request.
+ */
@NonNull
public String getKey() {
return mKey;
@@ -111,23 +128,23 @@ public final class AuthenticationEntry implements Parcelable {
return mSubkey;
}
- /**
- * Returns the Slice to be rendered.
- */
+ /** Returns the Slice to be rendered. */
@NonNull
public Slice getSlice() {
return mSlice;
}
- /**
- * Returns the entry status.
- */
+ /** Returns the entry status, depending on which the entry will be rendered differently. */
@NonNull
@Status
public int getStatus() {
return mStatus;
}
+ /**
+ * Returns the framework intent to be filled in when launching this entry's provider
+ * PendingIntent.
+ */
@Nullable
@SuppressLint("IntentBuilderName") // Not building a new intent.
public Intent getFrameworkExtrasIntent() {
diff --git a/core/java/android/credentials/ui/BaseDialogResult.java b/core/java/android/credentials/ui/BaseDialogResult.java
index e8cf5abd5239..e985a4666d31 100644
--- a/core/java/android/credentials/ui/BaseDialogResult.java
+++ b/core/java/android/credentials/ui/BaseDialogResult.java
@@ -24,8 +24,6 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.AnnotationValidations;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -46,7 +44,7 @@ public class BaseDialogResult implements Parcelable {
/**
* Used for the UX to construct the {@code resultData Bundle} to send via the {@code
- * ResultReceiver}.
+ * ResultReceiver}.
*/
public static void addToBundle(@NonNull BaseDialogResult result, @NonNull Bundle bundle) {
bundle.putParcelable(EXTRA_BASE_RESULT, result);
@@ -66,13 +64,14 @@ public class BaseDialogResult implements Parcelable {
RESULT_CODE_DATA_PARSING_FAILURE,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ResultCode {}
+ public @interface ResultCode {
+ }
/** User intentionally canceled the dialog. */
public static final int RESULT_CODE_DIALOG_USER_CANCELED = 0;
/**
- * The user has consented to switching to a new default provider. The provider info is in the
- * {@code resultData}.
+ * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+ * their providers.
*/
public static final int RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 1;
/**
@@ -86,6 +85,7 @@ public class BaseDialogResult implements Parcelable {
public static final int RESULT_CODE_DATA_PARSING_FAILURE = 3;
@Nullable
+ @Deprecated
private final IBinder mRequestToken;
public BaseDialogResult(@Nullable IBinder requestToken) {
@@ -94,6 +94,7 @@ public class BaseDialogResult implements Parcelable {
/** Returns the unique identifier for the request that launched the operation. */
@Nullable
+ @Deprecated
public IBinder getRequestToken() {
return mRequestToken;
}
@@ -115,14 +116,14 @@ public class BaseDialogResult implements Parcelable {
public static final @NonNull Creator<BaseDialogResult> CREATOR =
new Creator<BaseDialogResult>() {
- @Override
- public BaseDialogResult createFromParcel(@NonNull Parcel in) {
- return new BaseDialogResult(in);
- }
-
- @Override
- public BaseDialogResult[] newArray(int size) {
- return new BaseDialogResult[size];
- }
- };
+ @Override
+ public BaseDialogResult createFromParcel(@NonNull Parcel in) {
+ return new BaseDialogResult(in);
+ }
+
+ @Override
+ public BaseDialogResult[] newArray(int size) {
+ return new BaseDialogResult[size];
+ }
+ };
}
diff --git a/core/java/android/credentials/ui/CancelUiRequest.java b/core/java/android/credentials/ui/CancelUiRequest.java
index d4c249e58c8a..712424ced41b 100644
--- a/core/java/android/credentials/ui/CancelUiRequest.java
+++ b/core/java/android/credentials/ui/CancelUiRequest.java
@@ -24,7 +24,7 @@ import android.os.Parcelable;
import com.android.internal.util.AnnotationValidations;
/**
- * A request to cancel any ongoing UI matching this request.
+ * A request to cancel the ongoing UI matching the identifier token in this request.
*
* @hide
*/
@@ -33,9 +33,12 @@ public final class CancelUiRequest implements Parcelable {
/**
* The intent extra key for the {@code CancelUiRequest} object when launching the UX
* activities.
+ *
+ * @hide
*/
- @NonNull public static final String EXTRA_CANCEL_UI_REQUEST =
- "android.credentials.ui.extra.EXTRA_CANCEL_UI_REQUEST";
+ @NonNull
+ public static final String EXTRA_CANCEL_UI_REQUEST =
+ "android.credentials.ui.extra.CANCEL_UI_REQUEST";
@NonNull
private final IBinder mToken;
@@ -51,6 +54,10 @@ public final class CancelUiRequest implements Parcelable {
return mToken;
}
+ /**
+ * Returns the app package name invoking this request, that can be used to derive display
+ * metadata (e.g. "Cancelled by `App Name`").
+ */
@NonNull
public String getAppPackageName() {
return mAppPackageName;
@@ -64,6 +71,7 @@ public final class CancelUiRequest implements Parcelable {
return mShouldShowCancellationUi;
}
+ /** Constructs a {@link CancelUiRequest}. */
public CancelUiRequest(@NonNull IBinder token, boolean shouldShowCancellationUi,
@NonNull String appPackageName) {
mToken = token;
@@ -91,7 +99,8 @@ public final class CancelUiRequest implements Parcelable {
return 0;
}
- @NonNull public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
+ @NonNull
+ public static final Creator<CancelUiRequest> CREATOR = new Creator<>() {
@Override
public CancelUiRequest createFromParcel(@NonNull Parcel in) {
return new CancelUiRequest(in);
diff --git a/core/java/android/credentials/ui/Constants.java b/core/java/android/credentials/ui/Constants.java
index 37f850bc46c5..68f28e74dad0 100644
--- a/core/java/android/credentials/ui/Constants.java
+++ b/core/java/android/credentials/ui/Constants.java
@@ -36,7 +36,5 @@ public class Constants {
public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
"android.credentials.ui.extra.REQ_FOR_ALL_OPTIONS";
- /** The intent action for when the enabled Credential Manager providers has been updated. */
- public static final String CREDMAN_ENABLED_PROVIDERS_UPDATED =
- "android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED";
+ private Constants() {}
}
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 2508d8eb20ab..d7a4f5bdbfca 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -47,6 +47,17 @@ public final class CreateCredentialProviderData extends ProviderData implements
mRemoteEntry = remoteEntry;
}
+ /**
+ * Converts the instance to a {@link CreateCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ @NonNull
+ public CreateCredentialProviderInfo toCreateCredentialProviderInfo() {
+ return new CreateCredentialProviderInfo(
+ getProviderFlattenedComponentName(), mSaveEntries, mRemoteEntry);
+ }
+
@NonNull
public List<Entry> getSaveEntries() {
return mSaveEntries;
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderInfo.java b/core/java/android/credentials/ui/CreateCredentialProviderInfo.java
new file mode 100644
index 000000000000..41ca852c2351
--- /dev/null
+++ b/core/java/android/credentials/ui/CreateCredentialProviderInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 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.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class CreateCredentialProviderInfo {
+
+ @NonNull
+ private final String mProviderName;
+
+ @NonNull
+ private final List<Entry> mSaveEntries;
+ @Nullable
+ private final Entry mRemoteEntry;
+
+ CreateCredentialProviderInfo(
+ @NonNull String providerName, @NonNull List<Entry> saveEntries,
+ @Nullable Entry remoteEntry) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ mSaveEntries = new ArrayList<>(saveEntries);
+ mRemoteEntry = remoteEntry;
+ }
+
+ /** Returns the fully-qualified provider (component or package) name. */
+ @NonNull
+ public String getProviderName() {
+ return mProviderName;
+ }
+
+ /** Returns all the options this provider has, to which the credential can be saved. */
+ @NonNull
+ public List<Entry> getSaveEntries() {
+ return mSaveEntries;
+ }
+
+ /**
+ * Returns the remote credential saving option, if any.
+ *
+ * Notice that only one system configured provider can set this option, and when set, it means
+ * that the system service has already validated the provider's eligibility.
+ */
+ @Nullable
+ public Entry getRemoteEntry() {
+ return mRemoteEntry;
+ }
+
+ /**
+ * Builder for {@link CreateCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @NonNull
+ private String mProviderName;
+ @NonNull
+ private List<Entry> mSaveEntries = new ArrayList<>();
+ @Nullable
+ private Entry mRemoteEntry = null;
+
+ /** Constructor with required properties. */
+ public Builder(@NonNull String providerName) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ }
+
+ /** Sets the list of options for credential saving to be displayed to the user. */
+ @NonNull
+ public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
+ mSaveEntries = credentialEntries;
+ return this;
+ }
+
+ /** Sets the remote entry of the provider. */
+ @NonNull
+ public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+ mRemoteEntry = remoteEntry;
+ return this;
+ }
+
+ /** Builds a {@link CreateCredentialProviderInfo}. */
+ @NonNull
+ public CreateCredentialProviderInfo build() {
+ return new CreateCredentialProviderInfo(mProviderName, mSaveEntries, mRemoteEntry);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/DisabledProviderData.java b/core/java/android/credentials/ui/DisabledProviderData.java
index c266fd56acef..8bccdc9a199f 100644
--- a/core/java/android/credentials/ui/DisabledProviderData.java
+++ b/core/java/android/credentials/ui/DisabledProviderData.java
@@ -34,6 +34,16 @@ public final class DisabledProviderData extends ProviderData implements Parcelab
super(providerFlattenedComponentName);
}
+ /**
+ * Converts the instance to a {@link DisabledProviderInfo}.
+ *
+ * @hide
+ */
+ @NonNull
+ public DisabledProviderInfo toDisabledProviderInfo() {
+ return new DisabledProviderInfo(getProviderFlattenedComponentName());
+ }
+
private DisabledProviderData(@NonNull Parcel in) {
super(in);
}
diff --git a/core/java/android/credentials/ui/DisabledProviderInfo.java b/core/java/android/credentials/ui/DisabledProviderInfo.java
new file mode 100644
index 000000000000..7ce63681cf1c
--- /dev/null
+++ b/core/java/android/credentials/ui/DisabledProviderInfo.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 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.credentials.ui;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Information pertaining to a specific provider that is disabled from the user settings.
+ *
+ * Currently, disabled provider data is only propagated in the create-credential flow.
+ *
+ * @hide
+ */
+public final class DisabledProviderInfo {
+
+ @NonNull
+ private final String mProviderName;
+
+ /**
+ * Constructs a {@link DisabledProviderInfo}.
+ *
+ * @throws IllegalArgumentException if {@code providerName} is empty
+ */
+ public DisabledProviderInfo(
+ @NonNull String providerName) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ }
+
+ /** Returns the fully-qualified provider (component or package) name. */
+ @NonNull
+ public String getProviderName() {
+ return mProviderName;
+ }
+}
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 55f2a3eb490b..84694471ce70 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -35,10 +35,14 @@ import com.android.internal.util.AnnotationValidations;
*/
@TestApi
public final class Entry implements Parcelable {
- @NonNull private final String mKey;
- @NonNull private final String mSubkey;
- @Nullable private PendingIntent mPendingIntent;
- @Nullable private Intent mFrameworkExtrasIntent;
+ @NonNull
+ private final String mKey;
+ @NonNull
+ private final String mSubkey;
+ @Nullable
+ private PendingIntent mPendingIntent;
+ @Nullable
+ private Intent mFrameworkExtrasIntent;
@NonNull
private final Slice mSlice;
@@ -58,16 +62,19 @@ public final class Entry implements Parcelable {
mFrameworkExtrasIntent = in.readTypedObject(Intent.CREATOR);
}
- /** Constructor to be used for an entry that does not require further activities
+ /**
+ * Constructor to be used for an entry that does not require further activities
* to be invoked when selected.
*/
+ // TODO(b/322065508): deprecate this constructor.
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice) {
mKey = key;
mSubkey = subkey;
mSlice = slice;
}
- /** Constructor to be used for an entry that requires a pending intent to be invoked
+ /**
+ * Constructor to be used for an entry that requires a pending intent to be invoked
* when clicked.
*/
public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
@@ -77,9 +84,12 @@ public final class Entry implements Parcelable {
}
/**
- * Returns the identifier of this entry that's unique within the context of the CredentialManager
- * request.
- */
+ * Returns the identifier of this entry that's unique within the context of the
+ * CredentialManager
+ * request.
+ *
+ * Generally used when sending the user selection result back to the system service.
+ */
@NonNull
public String getKey() {
return mKey;
@@ -87,25 +97,33 @@ public final class Entry implements Parcelable {
/**
* Returns the sub-identifier of this entry that's unique within the context of the {@code key}.
+ *
+ * Generally used when sending the user selection result back to the system service.
*/
@NonNull
public String getSubkey() {
return mSubkey;
}
- /**
- * Returns the Slice to be rendered.
- */
+ /** Returns the Slice to be rendered. */
@NonNull
public Slice getSlice() {
return mSlice;
}
+ /**
+ * Returns the provider PendingIntent to launch once this entry is selected.
+ */
+ // TODO(b/322065508): deprecate this bit.
@Nullable
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
+ /**
+ * Returns the framework fill in intent to add to the provider PendingIntent to launch, once
+ * this entry is selected.
+ */
@Nullable
@SuppressLint("IntentBuilderName") // Not building a new intent.
public Intent getFrameworkExtrasIntent() {
@@ -126,7 +144,7 @@ public final class Entry implements Parcelable {
return 0;
}
- public static final @NonNull Creator<Entry> CREATOR = new Creator<Entry>() {
+ public static final @NonNull Creator<Entry> CREATOR = new Creator<>() {
@Override
public Entry createFromParcel(@NonNull Parcel in) {
return new Entry(in);
diff --git a/core/java/android/credentials/ui/FailureDialogResult.java b/core/java/android/credentials/ui/FailureDialogResult.java
new file mode 100644
index 000000000000..abd5a92415d8
--- /dev/null
+++ b/core/java/android/credentials/ui/FailureDialogResult.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Result data when the selector UI has encountered a failure.
+ *
+ * @hide
+ */
+public final class FailureDialogResult extends BaseDialogResult implements Parcelable {
+ /** Parses and returns a UserSelectionDialogResult from the given resultData. */
+ @Nullable
+ public static FailureDialogResult fromResultData(@NonNull Bundle resultData) {
+ return resultData.getParcelable(
+ EXTRA_FAILURE_RESULT, FailureDialogResult.class);
+ }
+
+ /**
+ * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
+ * ResultReceiver}.
+ */
+ public static void addToBundle(
+ @NonNull FailureDialogResult result, @NonNull Bundle bundle) {
+ bundle.putParcelable(EXTRA_FAILURE_RESULT, result);
+ }
+
+ /**
+ * The intent extra key for the {@code UserSelectionDialogResult} object when the credential
+ * selector activity finishes.
+ */
+ private static final String EXTRA_FAILURE_RESULT =
+ "android.credentials.ui.extra.FAILURE_RESULT";
+
+ @Nullable
+ private final String mErrorMessage;
+
+ public FailureDialogResult(@Nullable IBinder requestToken, @Nullable String errorMessage) {
+ super(requestToken);
+ mErrorMessage = errorMessage;
+ }
+
+ /** Returns provider package name whose entry was selected by the user. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ protected FailureDialogResult(@NonNull Parcel in) {
+ super(in);
+ mErrorMessage = in.readString8();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString8(mErrorMessage);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<FailureDialogResult> CREATOR =
+ new Creator<>() {
+ @Override
+ public FailureDialogResult createFromParcel(@NonNull Parcel in) {
+ return new FailureDialogResult(in);
+ }
+
+ @Override
+ public FailureDialogResult[] newArray(int size) {
+ return new FailureDialogResult[size];
+ }
+ };
+}
diff --git a/core/java/android/credentials/ui/FailureResult.java b/core/java/android/credentials/ui/FailureResult.java
new file mode 100644
index 000000000000..ec584170fba2
--- /dev/null
+++ b/core/java/android/credentials/ui/FailureResult.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 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.credentials.ui;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Failure or cancellation result encountered during a UI flow.
+ *
+ * @hide
+ */
+public final class FailureResult implements UiResult {
+ @Nullable
+ private final String mErrorMessage;
+ @NonNull
+ private final int mErrorCode;
+
+ /** @hide **/
+ @IntDef(prefix = {"ERROR_CODE_"}, value = {
+ ERROR_CODE_DIALOG_CANCELED_BY_USER,
+ ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS,
+ ERROR_CODE_UI_FAILURE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ErrorCode {
+ }
+
+ /**
+ * The UI was stopped due to a failure, e.g. because it failed to parse the incoming data,
+ * or it encountered an irrecoverable internal issue.
+ */
+ public static final int ERROR_CODE_UI_FAILURE = 0;
+ /** The user intentionally canceled the dialog. */
+ public static final int ERROR_CODE_DIALOG_CANCELED_BY_USER = 1;
+ /**
+ * The UI was stopped since the user has chosen to navigate to the Settings UI to reconfigure
+ * their providers.
+ */
+ public static final int ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS = 2;
+
+ /**
+ * Constructs a {@link FailureResult}.
+ *
+ * @throws IllegalArgumentException if {@code providerId} is empty
+ */
+ public FailureResult(@ErrorCode int errorCode, @Nullable String errorMessage) {
+ mErrorCode = errorCode;
+ mErrorMessage = errorMessage;
+ }
+
+ /** Returns the error code. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /** Returns the error message. */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ FailureDialogResult toFailureDialogResult() {
+ return new FailureDialogResult(/*requestToken=*/null, mErrorMessage);
+ }
+
+ int errorCodeToResultCode() {
+ switch (mErrorCode) {
+ case ERROR_CODE_DIALOG_CANCELED_BY_USER:
+ return BaseDialogResult.RESULT_CODE_DIALOG_USER_CANCELED;
+ case ERROR_CODE_CANCELED_AND_LAUNCHED_SETTINGS:
+ return BaseDialogResult.RESULT_CODE_CANCELED_AND_LAUNCHED_SETTINGS;
+ default:
+ return BaseDialogResult.RESULT_CODE_DATA_PARSING_FAILURE;
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index 181475c7ce5a..481419b4f732 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -55,6 +55,17 @@ public final class GetCredentialProviderData extends ProviderData implements Par
mRemoteEntry = remoteEntry;
}
+ /**
+ * Converts the instance to a {@link GetCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ @NonNull
+ public GetCredentialProviderInfo toGetCredentialProviderInfo() {
+ return new GetCredentialProviderInfo(getProviderFlattenedComponentName(),
+ mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+ }
+
@NonNull
public List<Entry> getCredentialEntries() {
return mCredentialEntries;
@@ -83,12 +94,12 @@ public final class GetCredentialProviderData extends ProviderData implements Par
mCredentialEntries = credentialEntries;
AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
- List<Entry> actionChips = new ArrayList<>();
+ List<Entry> actionChips = new ArrayList<>();
in.readTypedList(actionChips, Entry.CREATOR);
mActionChips = actionChips;
AnnotationValidations.validate(NonNull.class, null, mActionChips);
- List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
+ List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
mAuthenticationEntries = authenticationEntries;
AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
@@ -113,16 +124,16 @@ public final class GetCredentialProviderData extends ProviderData implements Par
public static final @NonNull Creator<GetCredentialProviderData> CREATOR =
new Creator<GetCredentialProviderData>() {
- @Override
- public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
- return new GetCredentialProviderData(in);
- }
+ @Override
+ public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
+ return new GetCredentialProviderData(in);
+ }
- @Override
- public GetCredentialProviderData[] newArray(int size) {
- return new GetCredentialProviderData[size];
- }
- };
+ @Override
+ public GetCredentialProviderData[] newArray(int size) {
+ return new GetCredentialProviderData[size];
+ }
+ };
/**
* Builder for {@link GetCredentialProviderData}.
@@ -131,11 +142,16 @@ public final class GetCredentialProviderData extends ProviderData implements Par
*/
@TestApi
public static final class Builder {
- @NonNull private String mProviderFlattenedComponentName;
- @NonNull private List<Entry> mCredentialEntries = new ArrayList<>();
- @NonNull private List<Entry> mActionChips = new ArrayList<>();
- @NonNull private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
- @Nullable private Entry mRemoteEntry = null;
+ @NonNull
+ private String mProviderFlattenedComponentName;
+ @NonNull
+ private List<Entry> mCredentialEntries = new ArrayList<>();
+ @NonNull
+ private List<Entry> mActionChips = new ArrayList<>();
+ @NonNull
+ private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+ @Nullable
+ private Entry mRemoteEntry = null;
/** Constructor with required properties. */
public Builder(@NonNull String providerFlattenedComponentName) {
diff --git a/core/java/android/credentials/ui/GetCredentialProviderInfo.java b/core/java/android/credentials/ui/GetCredentialProviderInfo.java
new file mode 100644
index 000000000000..bac71472acd1
--- /dev/null
+++ b/core/java/android/credentials/ui/GetCredentialProviderInfo.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 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.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information pertaining to a specific provider during the given create-credential flow.
+ *
+ * This includes provider metadata and its credential creation options for display purposes.
+ *
+ * @hide
+ */
+public final class GetCredentialProviderInfo {
+
+ @NonNull
+ private final String mProviderName;
+
+ @NonNull
+ private final List<Entry> mCredentialEntries;
+ @NonNull
+ private final List<Entry> mActionChips;
+ @NonNull
+ private final List<AuthenticationEntry> mAuthenticationEntries;
+ @Nullable
+ private final Entry mRemoteEntry;
+
+ GetCredentialProviderInfo(
+ @NonNull String providerName, @NonNull List<Entry> credentialEntries,
+ @NonNull List<Entry> actionChips,
+ @NonNull List<AuthenticationEntry> authenticationEntries,
+ @Nullable Entry remoteEntry) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ mCredentialEntries = new ArrayList<>(credentialEntries);
+ mActionChips = new ArrayList<>(actionChips);
+ mAuthenticationEntries = new ArrayList<>(authenticationEntries);
+ mRemoteEntry = remoteEntry;
+ }
+
+ /** Returns the fully-qualified provider (component or package) name. */
+ @NonNull
+ public String getProviderName() {
+ return mProviderName;
+ }
+
+ /** Returns the display information for all the candidate credentials this provider has. */
+ @NonNull
+ public List<Entry> getCredentialEntries() {
+ return mCredentialEntries;
+ }
+
+ /**
+ * Returns a list of actions defined by the provider that intent into the provider's app for
+ * specific user actions, each of which should eventually lead to an actual credential.
+ */
+ @NonNull
+ public List<Entry> getActionChips() {
+ return mActionChips;
+ }
+
+ /**
+ * Returns a list of authentication actions that each intents into a provider authentication
+ * activity.
+ *
+ * When the authentication activity succeeds, the provider will return a list of actual
+ * credential candidates to render. However, the UI should not attempt to parse the result
+ * itself, but rather send the result back to the system service, which will then process the
+ * new candidates and relaunch the UI with updated display data.
+ */
+ @NonNull
+ public List<AuthenticationEntry> getAuthenticationEntries() {
+ return mAuthenticationEntries;
+ }
+
+ /**
+ * Returns the remote credential retrieval option, if any.
+ *
+ * Notice that only one system configured provider can set this option, and when set, it means
+ * that the system service has already validated the provider's eligibility.
+ */
+ @Nullable
+ public Entry getRemoteEntry() {
+ return mRemoteEntry;
+ }
+
+ /**
+ * Builder for {@link GetCredentialProviderInfo}.
+ *
+ * @hide
+ */
+ public static final class Builder {
+ @NonNull
+ private String mProviderName;
+ @NonNull
+ private List<Entry> mCredentialEntries = new ArrayList<>();
+ @NonNull
+ private List<Entry> mActionChips = new ArrayList<>();
+ @NonNull
+ private List<AuthenticationEntry> mAuthenticationEntries = new ArrayList<>();
+ @Nullable
+ private Entry mRemoteEntry = null;
+
+ /**
+ * Constructs a {@link GetCredentialProviderInfo.Builder}.
+ *
+ * @throws IllegalArgumentException if {@code providerName} is null or empty
+ */
+ public Builder(@NonNull String providerName) {
+ mProviderName = Preconditions.checkStringNotEmpty(providerName);
+ }
+
+ /** Sets the list of credential candidates to be displayed to the user. */
+ @NonNull
+ public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+ mCredentialEntries = credentialEntries;
+ return this;
+ }
+
+ /** Sets the list of action chips to be displayed to the user. */
+ @NonNull
+ public Builder setActionChips(@NonNull List<Entry> actionChips) {
+ mActionChips = actionChips;
+ return this;
+ }
+
+ /** Sets the authentication entry to be displayed to the user. */
+ @NonNull
+ public Builder setAuthenticationEntries(
+ @NonNull List<AuthenticationEntry> authenticationEntry) {
+ mAuthenticationEntries = authenticationEntry;
+ return this;
+ }
+
+ /** Sets the remote entry to be displayed to the user. */
+ @NonNull
+ public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+ mRemoteEntry = remoteEntry;
+ return this;
+ }
+
+ /** Builds a {@link GetCredentialProviderInfo}. */
+ @NonNull
+ public GetCredentialProviderInfo build() {
+ return new GetCredentialProviderInfo(mProviderName,
+ mCredentialEntries, mActionChips, mAuthenticationEntries, mRemoteEntry);
+ }
+ }
+}
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 49321d514128..5e1e0efe39c4 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -113,25 +113,6 @@ public class IntentFactory {
}
/**
- * Notify the UI that providers have been enabled/disabled.
- *
- * @hide
- */
- @NonNull
- public static Intent createProviderUpdateIntent() {
- Intent intent = new Intent();
- ComponentName componentName =
- ComponentName.unflattenFromString(
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_credentialManagerReceiverComponent));
- intent.setComponent(componentName);
- intent.setAction(Constants.CREDMAN_ENABLED_PROVIDERS_UPDATED);
- return intent;
- }
-
- /**
* Convert an instance of a "locally-defined" ResultReceiver to an instance of {@link
* android.os.ResultReceiver} itself, which the receiving process will be able to unmarshall.
*/
diff --git a/core/java/android/credentials/ui/IntentHelper.java b/core/java/android/credentials/ui/IntentHelper.java
new file mode 100644
index 000000000000..c5f34c1440a7
--- /dev/null
+++ b/core/java/android/credentials/ui/IntentHelper.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.os.ResultReceiver;
+
+import java.util.List;
+
+/**
+ * Utilities for parsing the intent data used to launch the UI activity.
+ *
+ * @hide
+ */
+public final class IntentHelper {
+ /**
+ * Attempts to extract a {@link CancelUiRequest} from the given intent; returns null
+ * if not found.
+ */
+ @Nullable
+ public static CancelUiRequest extractCancelUiRequest(@NonNull Intent intent) {
+ return intent.getParcelableExtra(CancelUiRequest.EXTRA_CANCEL_UI_REQUEST,
+ CancelUiRequest.class);
+ }
+
+ /**
+ * Attempts to extract a {@link RequestInfo} from the given intent; returns null
+ * if not found.
+ */
+ @Nullable
+ public static RequestInfo extractRequestInfo(@NonNull Intent intent) {
+ return intent.getParcelableExtra(RequestInfo.EXTRA_REQUEST_INFO,
+ RequestInfo.class);
+ }
+
+ /**
+ * Attempts to extract the list of {@link GetCredentialProviderInfo} from the given intent;
+ * returns null if not found.
+ */
+ @Nullable
+ @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+ // and the other APIs in this class.
+ public static List<GetCredentialProviderInfo> extractGetCredentialProviderDataList(
+ @NonNull Intent intent) {
+ List<GetCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ GetCredentialProviderData.class);
+ return providerList == null ? null : providerList.stream().map(
+ GetCredentialProviderData::toGetCredentialProviderInfo).toList();
+ }
+
+ /**
+ * Attempts to extract the list of {@link CreateCredentialProviderInfo} from the given intent;
+ * returns null if not found.
+ */
+ @Nullable
+ @SuppressLint("NullableCollection") // To be consistent with the nullable Intent extra APIs
+ // and the other APIs in this class.
+ public static List<CreateCredentialProviderInfo> extractCreateCredentialProviderDataList(
+ @NonNull Intent intent) {
+ List<CreateCredentialProviderData> providerList = intent.getParcelableArrayListExtra(
+ ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+ CreateCredentialProviderData.class);
+ return providerList == null ? null : providerList.stream().map(
+ CreateCredentialProviderData::toCreateCredentialProviderInfo).toList();
+ }
+
+ /**
+ * Attempts to extract a {@link android.os.ResultReceiver} from the given intent, which should
+ * be used to send back UI results; returns null if not found.
+ */
+ @Nullable
+ public static ResultReceiver extractResultReceiver(@NonNull Intent intent) {
+ return intent.getParcelableExtra(Constants.EXTRA_RESULT_RECEIVER,
+ ResultReceiver.class);
+ }
+
+ private IntentHelper() {
+ }
+}
diff --git a/core/java/android/credentials/ui/ProviderDialogResult.java b/core/java/android/credentials/ui/ProviderDialogResult.java
deleted file mode 100644
index 53f1864f7bc6..000000000000
--- a/core/java/android/credentials/ui/ProviderDialogResult.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 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.credentials.ui;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.internal.util.AnnotationValidations;
-
-/**
- * Result data matching {@link BaseDialogResult#RESULT_CODE_PROVIDER_ENABLED}, or {@link
- * BaseDialogResult#RESULT_CODE_DEFAULT_PROVIDER_CHANGED}.
- *
- * @hide
- */
-public final class ProviderDialogResult extends BaseDialogResult implements Parcelable {
- /** Parses and returns a ProviderDialogResult from the given resultData. */
- @Nullable
- public static ProviderDialogResult fromResultData(@NonNull Bundle resultData) {
- return resultData.getParcelable(EXTRA_PROVIDER_RESULT, ProviderDialogResult.class);
- }
-
- /**
- * Used for the UX to construct the {@code resultData Bundle} to send via the {@code
- * ResultReceiver}.
- */
- public static void addToBundle(
- @NonNull ProviderDialogResult result, @NonNull Bundle bundle) {
- bundle.putParcelable(EXTRA_PROVIDER_RESULT, result);
- }
-
- /**
- * The intent extra key for the {@code ProviderDialogResult} object when the credential
- * selector activity finishes.
- */
- private static final String EXTRA_PROVIDER_RESULT =
- "android.credentials.ui.extra.PROVIDER_RESULT";
-
- @NonNull
- private final String mProviderId;
-
- public ProviderDialogResult(@NonNull IBinder requestToken, @NonNull String providerId) {
- super(requestToken);
- mProviderId = providerId;
- }
-
- @NonNull
- public String getProviderId() {
- return mProviderId;
- }
-
- protected ProviderDialogResult(@NonNull Parcel in) {
- super(in);
- String providerId = in.readString8();
- mProviderId = providerId;
- AnnotationValidations.validate(NonNull.class, null, mProviderId);
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeString8(mProviderId);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- public static final @NonNull Creator<ProviderDialogResult> CREATOR =
- new Creator<ProviderDialogResult>() {
- @Override
- public ProviderDialogResult createFromParcel(@NonNull Parcel in) {
- return new ProviderDialogResult(in);
- }
-
- @Override
- public ProviderDialogResult[] newArray(int size) {
- return new ProviderDialogResult[size];
- }
- };
-}
diff --git a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
index 47936c48f927..11cc21f9d2db 100644
--- a/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
+++ b/core/java/android/credentials/ui/ProviderPendingIntentResponse.java
@@ -16,15 +16,20 @@
package android.credentials.ui;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Parcel;
import android.os.Parcelable;
-import androidx.annotation.NonNull;
-
/**
- * Response from a provider's pending intent
+ * Result of launching a provider's PendingIntent associated with an {@link Entry} after it is
+ * selected by the user.
+ *
+ * The provider sets the credential creation / retrieval result through
+ * {@link android.app.Activity#setResult(int, Intent)}, which is then directly propagated back
+ * through this data structure.
*
* @hide
*/
@@ -33,20 +38,21 @@ public final class ProviderPendingIntentResponse implements Parcelable {
@Nullable
private final Intent mResultData;
+ /** Constructs a {@link ProviderPendingIntentResponse}. */
public ProviderPendingIntentResponse(int resultCode, @Nullable Intent resultData) {
mResultCode = resultCode;
mResultData = resultData;
}
- protected ProviderPendingIntentResponse(Parcel in) {
+ private ProviderPendingIntentResponse(@NonNull Parcel in) {
mResultCode = in.readInt();
mResultData = in.readTypedObject(Intent.CREATOR);
}
- public static final Creator<ProviderPendingIntentResponse> CREATOR =
- new Creator<ProviderPendingIntentResponse>() {
+ public static final @NonNull Creator<ProviderPendingIntentResponse> CREATOR =
+ new Creator<>() {
@Override
- public ProviderPendingIntentResponse createFromParcel(Parcel in) {
+ public ProviderPendingIntentResponse createFromParcel(@NonNull Parcel in) {
return new ProviderPendingIntentResponse(in);
}
@@ -67,13 +73,15 @@ public final class ProviderPendingIntentResponse implements Parcelable {
dest.writeTypedObject(mResultData, flags);
}
- /** Returns the result code associated with this pending intent activity result. */
+ /** Returns the result code associated with this provider PendingIntent activity result. */
public int getResultCode() {
return mResultCode;
}
- /** Returns the result data associated with this pending intent activity result. */
- @NonNull public Intent getResultData() {
+ /** Returns the result data associated with this provider PendingIntent activity result. */
+ @SuppressLint("IntentBuilderName") // Not building a new intent.
+ @NonNull
+ public Intent getResultData() {
return mResultData;
}
}
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 4fedc8353bf7..f65158444e48 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -48,17 +48,24 @@ public final class RequestInfo implements Parcelable {
@NonNull public static final String EXTRA_REQUEST_INFO =
"android.credentials.ui.extra.REQUEST_INFO";
- /** Type value for any request that does not require UI. */
+ /**
+ * Type value for any request that does not require UI.
+ */
@NonNull public static final String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
- /** Type value for a getCredential request. */
+ /**
+ * Type value for a getCredential request.
+ */
@NonNull public static final String TYPE_GET = "android.credentials.ui.TYPE_GET";
- /** Type value for a getCredential request that utilizes the credential registry.
+ /**
+ * Type value for a getCredential request that utilizes the credential registry.
*
* @hide
- **/
+ */
@NonNull public static final String TYPE_GET_VIA_REGISTRY =
"android.credentials.ui.TYPE_GET_VIA_REGISTRY";
- /** Type value for a createCredential request. */
+ /**
+ * Type value for a createCredential request.
+ */
@NonNull public static final String TYPE_CREATE = "android.credentials.ui.TYPE_CREATE";
/** @hide */
diff --git a/core/java/android/credentials/ui/ResultHelper.java b/core/java/android/credentials/ui/ResultHelper.java
new file mode 100644
index 000000000000..7b9d5e87d666
--- /dev/null
+++ b/core/java/android/credentials/ui/ResultHelper.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+
+/**
+ * Utilities for sending the UI results back to the system service.
+ *
+ * @hide
+ */
+public final class ResultHelper {
+ /**
+ * Sends the {@code failureResult} that caused the UI to stop back to the CredentialManager
+ * service.
+ *
+ * The {code resultReceiver} for a UI flow can be extracted from the UI launch intent via
+ * {@link IntentHelper#extractResultReceiver(Intent)}.
+ */
+ public static void sendFailureResult(@NonNull ResultReceiver resultReceiver,
+ @NonNull FailureResult failureResult) {
+ FailureDialogResult result = failureResult.toFailureDialogResult();
+ Bundle resultData = new Bundle();
+ FailureDialogResult.addToBundle(result, resultData);
+ resultReceiver.send(failureResult.errorCodeToResultCode(),
+ resultData);
+ }
+
+ /**
+ * Sends the completed {@code userSelectionResult} back to the CredentialManager service.
+ *
+ * The {code resultReceiver} for a UI flow can be extracted from the UI launch intent via
+ * {@link IntentHelper#extractResultReceiver(Intent)}.
+ */
+ public static void sendUserSelectionResult(@NonNull ResultReceiver resultReceiver,
+ @NonNull UserSelectionResult userSelectionResult) {
+ UserSelectionDialogResult result = userSelectionResult.toUserSelectionDialogResult();
+ Bundle resultData = new Bundle();
+ UserSelectionDialogResult.addToBundle(result, resultData);
+ resultReceiver.send(BaseDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION,
+ resultData);
+ }
+
+ private ResultHelper() {}
+}
diff --git a/core/java/android/credentials/ui/UiResult.java b/core/java/android/credentials/ui/UiResult.java
new file mode 100644
index 000000000000..692584d1a561
--- /dev/null
+++ b/core/java/android/credentials/ui/UiResult.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+/**
+ * Base class for different types of ui results.
+ *
+ * @hide
+ */
+public interface UiResult {}
diff --git a/core/java/android/credentials/ui/UserSelectionResult.java b/core/java/android/credentials/ui/UserSelectionResult.java
new file mode 100644
index 000000000000..431dc631f3f6
--- /dev/null
+++ b/core/java/android/credentials/ui/UserSelectionResult.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 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.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Result sent back from the UI after the user chose an option and completed the following
+ * transaction launched through the provider PendingIntent associated with that option.
+ *
+ * @hide
+ */
+public final class UserSelectionResult implements UiResult {
+ @NonNull
+ private final String mProviderId;
+ @NonNull
+ private final String mEntryKey;
+ @NonNull
+ private final String mEntrySubkey;
+ @Nullable
+ private ProviderPendingIntentResponse mProviderPendingIntentResponse;
+
+ /**
+ * Constructs a {@link UserSelectionResult}.
+ *
+ * @throws IllegalArgumentException if {@code providerId} is empty
+ */
+
+ public UserSelectionResult(@NonNull String providerId,
+ @NonNull String entryKey, @NonNull String entrySubkey,
+ @Nullable ProviderPendingIntentResponse providerPendingIntentResponse) {
+ mProviderId = Preconditions.checkStringNotEmpty(providerId);
+ mEntryKey = Preconditions.checkNotNull(entryKey);
+ mEntrySubkey = Preconditions.checkNotNull(entrySubkey);
+ mProviderPendingIntentResponse = providerPendingIntentResponse;
+ }
+
+ /** Returns provider package name whose entry was selected by the user. */
+ @NonNull
+ public String getProviderId() {
+ return mProviderId;
+ }
+
+ /** Returns the key of the visual entry that the user selected. */
+ @NonNull
+ public String getEntryKey() {
+ return mEntryKey;
+ }
+
+ /** Returns the subkey of the visual entry that the user selected. */
+ @NonNull
+ public String getEntrySubkey() {
+ return mEntrySubkey;
+ }
+
+ /** Returns the pending intent response from the provider. */
+ @Nullable
+ public ProviderPendingIntentResponse getPendingIntentProviderResponse() {
+ return mProviderPendingIntentResponse;
+ }
+
+ @NonNull
+ UserSelectionDialogResult toUserSelectionDialogResult() {
+ return new UserSelectionDialogResult(/*requestToken=*/null, mProviderId, mEntryKey,
+ mEntrySubkey, mProviderPendingIntentResponse);
+ }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 0047b7d69282..ce0f9f598897 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -115,14 +115,16 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable {
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int R_8 = 0x38;
/**
- * Format: 16 bits red. Bits should be represented in unsigned integer, instead of the
- * implicit unsigned normalized.
+ * Format: 16 bits red. When sampled on the GPU this is represented as an
+ * unsigned integer instead of implicit unsigned normalize.
+ * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
*/
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int R_16 = 0x39;
/**
- * Format: 16 bits each red, green. Bits should be represented in unsigned integer,
- * instead of the implicit unsigned normalized.
+ * Format: 16 bits each red, green. When sampled on the GPU this is represented
+ * as an unsigned integer instead of implicit unsigned normalize.
+ * For more information see https://www.khronos.org/opengl/wiki/Normalized_Integer
*/
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int RG_1616 = 0x3a;
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
index 73ac333cfd89..d51e62e709c2 100644
--- a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -33,4 +33,20 @@ oneway interface AuthenticationStateListener {
* Defines behavior in response to authentication stopping
*/
void onAuthenticationStopped();
+
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationSucceeded(int requestReason, int userId);
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ void onAuthenticationFailed(int requestReason, int userId);
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index c0424dbeb813..bdaf9d789960 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,7 +16,7 @@
package android.hardware.biometrics;
-import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -174,9 +174,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @return This builder.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@NonNull
- public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) {
+ public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
mPromptInfo.setLogoRes(logoRes);
return this;
}
@@ -193,9 +193,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @return This builder.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@NonNull
- public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) {
+ public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
mPromptInfo.setLogoBitmap(logoBitmap);
return this;
}
@@ -719,25 +719,25 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
/**
* Gets the drawable resource of the logo for the prompt, as set by
- * {@link Builder#setLogo(int)}. Currently for system applications use only.
+ * {@link Builder#setLogoRes(int)}. Currently for system applications use only.
*
* @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@DrawableRes
public int getLogoRes() {
return mPromptInfo.getLogoRes();
}
/**
- * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for
- * system applications use only.
+ * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogoBitmap(Bitmap)}.
+ * Currently for system applications use only.
*
* @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
- @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
@Nullable
public Bitmap getLogoBitmap() {
return mPromptInfo.getLogoBitmap();
diff --git a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
index c5e5a8076747..25e5cca485d2 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -28,14 +28,14 @@ import android.os.Parcelable;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
- private final CharSequence mText;
+ private final String mText;
/**
* A list item with bulleted text shown on {@link PromptVerticalListContentView}.
*
* @param text The text of this list item.
*/
- public PromptContentItemBulletedText(@NonNull CharSequence text) {
+ public PromptContentItemBulletedText(@NonNull String text) {
mText = text;
}
@@ -43,7 +43,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar
* @hide
*/
@NonNull
- public CharSequence getText() {
+ public String getText() {
return mText;
}
@@ -60,7 +60,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mText);
+ dest.writeString(mText);
}
/**
@@ -70,7 +70,7 @@ public final class PromptContentItemBulletedText implements PromptContentItemPar
public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() {
@Override
public PromptContentItemBulletedText createFromParcel(Parcel in) {
- return new PromptContentItemBulletedText(in.readCharSequence());
+ return new PromptContentItemBulletedText(in.readString());
}
@Override
diff --git a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
index 6434c5975c12..7919256f9c6d 100644
--- a/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -28,14 +28,14 @@ import android.os.Parcelable;
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
public final class PromptContentItemPlainText implements PromptContentItemParcelable {
- private final CharSequence mText;
+ private final String mText;
/**
* A list item with plain text shown on {@link PromptVerticalListContentView}.
*
* @param text The text of this list item.
*/
- public PromptContentItemPlainText(@NonNull CharSequence text) {
+ public PromptContentItemPlainText(@NonNull String text) {
mText = text;
}
@@ -43,7 +43,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel
* @hide
*/
@NonNull
- public CharSequence getText() {
+ public String getText() {
return mText;
}
@@ -60,7 +60,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel
*/
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeCharSequence(mText);
+ dest.writeString(mText);
}
/**
@@ -70,7 +70,7 @@ public final class PromptContentItemPlainText implements PromptContentItemParcel
public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() {
@Override
public PromptContentItemPlainText createFromParcel(Parcel in) {
- return new PromptContentItemPlainText(in.readCharSequence());
+ return new PromptContentItemPlainText(in.readString());
}
@Override
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index d788b37c781d..0f9cadc52608 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -166,9 +166,9 @@ public class PromptInfo implements Parcelable {
}
/**
- * Returns whether MANAGE_BIOMETRIC_DIALOG is contained.
+ * Returns whether SET_BIOMETRIC_DIALOG_LOGO is contained.
*/
- public boolean containsManageBioApiConfigurations() {
+ public boolean containsSetLogoApiConfigurations() {
if (mLogoRes != -1) {
return true;
} else if (mLogoBitmap != null) {
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index f3e62907d845..38d32dc73ccb 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -52,11 +52,11 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
private static final int MAX_ITEM_NUMBER = 20;
private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
private final List<PromptContentItemParcelable> mContentList;
- private final CharSequence mDescription;
+ private final String mDescription;
private PromptVerticalListContentView(
@NonNull List<PromptContentItemParcelable> contentList,
- @NonNull CharSequence description) {
+ @NonNull String description) {
mContentList = contentList;
mDescription = description;
}
@@ -65,7 +65,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
mContentList = in.readArrayList(
PromptContentItemParcelable.class.getClassLoader(),
PromptContentItemParcelable.class);
- mDescription = in.readCharSequence();
+ mDescription = in.readString();
}
/**
@@ -84,12 +84,12 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
/**
* Gets the description for the content view, as set by
- * {@link PromptVerticalListContentView.Builder#setDescription(CharSequence)}.
+ * {@link PromptVerticalListContentView.Builder#setDescription(String)}.
*
* @return The description for the content view, or null if the content view has no description.
*/
@Nullable
- public CharSequence getDescription() {
+ public String getDescription() {
return mDescription;
}
@@ -118,7 +118,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
@Override
public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
dest.writeList(mContentList);
- dest.writeCharSequence(mDescription);
+ dest.writeString(mDescription);
}
/**
@@ -143,7 +143,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
*/
public static final class Builder {
private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
- private CharSequence mDescription;
+ private String mDescription;
/**
* Optional: Sets a description that will be shown on the content view.
@@ -152,7 +152,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
* @return This builder.
*/
@NonNull
- public Builder setDescription(@NonNull CharSequence description) {
+ public Builder setDescription(@NonNull String description) {
mDescription = description;
return this;
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index e267e6b22f9d..8e234fa11866 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -15,6 +15,7 @@
*/
package android.hardware.face;
+import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.hardware.biometrics.IBiometricStateListener;
@@ -181,6 +182,14 @@ interface IFaceService {
// authenticators. The callback is automatically removed after it's invoked.
void addAuthenticatorsRegisteredCallback(IFaceAuthenticatorsRegisteredCallback callback);
+ // Registers AuthenticationStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+ // Unregisters AuthenticationStateListener.
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+ void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
// Registers BiometricStateListener.
void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index d93953231eaf..fdbd3197fb79 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -17,6 +17,7 @@
package android.hardware.input;
import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
@@ -68,6 +69,12 @@ public class InputSettings {
*/
public static final int MAX_ACCESSIBILITY_BOUNCE_KEYS_THRESHOLD_MILLIS = 5000;
+ /**
+ * The maximum allowed Accessibility slow keys threshold.
+ * @hide
+ */
+ public static final int MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS = 5000;
+
private InputSettings() {
}
@@ -419,6 +426,86 @@ public class InputSettings {
}
/**
+ * Whether Accessibility slow keys feature flags is enabled.
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ public static boolean isAccessibilitySlowKeysFeatureFlagEnabled() {
+ return keyboardA11ySlowKeysFlag() && enableInputFilterRustImpl();
+ }
+
+ /**
+ * Whether Accessibility slow keys is enabled.
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ public static boolean isAccessibilitySlowKeysEnabled(@NonNull Context context) {
+ return getAccessibilitySlowKeysThreshold(context) != 0;
+ }
+
+ /**
+ * Get Accessibility slow keys threshold duration in milliseconds.
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ public static int getAccessibilitySlowKeysThreshold(@NonNull Context context) {
+ if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ return 0;
+ }
+ return Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SLOW_KEYS, 0, UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Set Accessibility slow keys threshold duration in milliseconds.
+ * @param thresholdTimeMillis time duration for which a key should be pressed to be registered
+ * in the system. The threshold must be between 0 and
+ * {@link MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS}
+ *
+ * <p>
+ * 'Slow keys' is an accessibility feature to aid users who have physical disabilities, that
+ * allows the user to specify the duration for which one must press-and-hold a key before the
+ * system accepts the keypress.
+ * </p>
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setAccessibilitySlowKeysThreshold(@NonNull Context context,
+ int thresholdTimeMillis) {
+ if (!isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ return;
+ }
+ if (thresholdTimeMillis < 0
+ || thresholdTimeMillis > MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS) {
+ throw new IllegalArgumentException(
+ "Provided Slow keys threshold should be in range [0, "
+ + MAX_ACCESSIBILITY_SLOW_KEYS_THRESHOLD_MILLIS + "]");
+ }
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_SLOW_KEYS, thresholdTimeMillis,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
* Whether Accessibility sticky keys feature is enabled.
*
* <p>
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 362fe78b14b8..0ed6569afd2a 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -29,4 +29,11 @@ flag {
name: "pointer_coords_is_resampled_api"
description: "Makes MotionEvent.PointerCoords#isResampled() a public API"
bug: "298197511"
+}
+
+flag {
+ namespace: "input_native"
+ name: "keyboard_a11y_slow_keys_flag"
+ description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
+ bug: "294546335"
} \ No newline at end of file
diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java
index 8644d9103dcb..f65b713f8967 100644
--- a/core/java/android/metrics/LogMaker.java
+++ b/core/java/android/metrics/LogMaker.java
@@ -32,6 +32,7 @@ import java.util.Arrays;
* @hide
*/
@SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class LogMaker {
private static final String TAG = "LogBuilder";
diff --git a/core/java/android/net/thread/OWNERS b/core/java/android/net/thread/OWNERS
new file mode 100644
index 000000000000..55c307b5eb62
--- /dev/null
+++ b/core/java/android/net/thread/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1203089
+
+include platform/packages/modules/ThreadNetwork:/OWNERS
diff --git a/core/java/android/net/thread/flags.aconfig b/core/java/android/net/thread/flags.aconfig
new file mode 100644
index 000000000000..6e72f8ebd8d1
--- /dev/null
+++ b/core/java/android/net/thread/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.net.thread.flags"
+
+flag {
+ name: "thread_user_restriction_enabled"
+ namespace: "thread_network"
+ description: "Controls whether user restriction on thread networks is enabled"
+ bug: "307679182"
+}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 58717179d64d..3977bdf413d9 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -28,6 +28,7 @@ import android.app.ActivityThread;
import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.sysprop.DeviceProperties;
import android.sysprop.SocProperties;
import android.sysprop.TelephonyProperties;
@@ -47,6 +48,7 @@ import java.util.stream.Collectors;
/**
* Information about the current build, extracted from system properties.
*/
+@RavenwoodKeepWholeClass
public class Build {
private static final String TAG = "Build";
@@ -307,7 +309,7 @@ public class Build {
* compatibility.
*/
final String[] abiList;
- if (VMRuntime.getRuntime().is64Bit()) {
+ if (android.os.Process.is64Bit()) {
abiList = SUPPORTED_64_BIT_ABIS;
} else {
abiList = SUPPORTED_32_BIT_ABIS;
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index 02704f5b346b..236194d16ad8 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -294,6 +294,43 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
XmlUtils.writeMapXml(mMap, out, this);
}
+ /**
+ * Checks whether all keys and values are within the given character limit.
+ * Note: Maximum character limit of String that can be saved to XML as part of bundle is 65535.
+ * Otherwise IOException is thrown.
+ * @param limit length of String keys and values in the PersistableBundle, including nested
+ * PersistableBundles to check against.
+ *
+ * @hide
+ */
+ public boolean isBundleContentsWithinLengthLimit(int limit) {
+ unparcel();
+ if (mMap == null) {
+ return true;
+ }
+ for (int i = 0; i < mMap.size(); i++) {
+ if (mMap.keyAt(i) != null && mMap.keyAt(i).length() > limit) {
+ return false;
+ }
+ final Object value = mMap.valueAt(i);
+ if (value instanceof String && ((String) value).length() > limit) {
+ return false;
+ } else if (value instanceof String[]) {
+ String[] stringArray = (String[]) value;
+ for (int j = 0; j < stringArray.length; j++) {
+ if (stringArray[j] != null
+ && stringArray[j].length() > limit) {
+ return false;
+ }
+ }
+ } else if (value instanceof PersistableBundle
+ && !((PersistableBundle) value).isBundleContentsWithinLengthLimit(limit)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/** @hide */
static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
@Override
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index dd0436cbb2f2..1f3a1620a9f2 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -1589,7 +1589,15 @@ public class Process {
@UnsupportedAppUsage
public static final native long getPss(int pid);
- /** @hide */
+ /**
+ * Gets the total Rss value for a given process, in bytes.
+ *
+ * @param pid the process to the Rss for
+ * @return an ordered array containing multiple values, they are:
+ * [total_rss, file, anon, swap, shmem].
+ * or NULL if the value cannot be determined
+ * @hide
+ */
public static final native long[] getRss(int pid);
/**
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index aa283a2d019b..a818919d184e 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -20,6 +20,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+import android.ravenwood.annotation.RavenwoodNativeSubstitutionClass;
import android.util.Log;
import android.util.MutableInt;
@@ -36,6 +38,8 @@ import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
/**
* Gives access to the system properties store. The system properties
@@ -51,6 +55,8 @@ import java.util.HashMap;
* {@hide}
*/
@SystemApi
+@RavenwoodKeepWholeClass
+@RavenwoodNativeSubstitutionClass("com.android.hoststubgen.nativesubstitution.SystemProperties_host")
public class SystemProperties {
private static final String TAG = "SystemProperties";
private static final boolean TRACK_KEY_ACCESS = false;
@@ -94,6 +100,31 @@ public class SystemProperties {
}
}
+ /** @hide */
+ public static void init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate) {
+ native_init$ravenwood(values, keyReadablePredicate, keyWritablePredicate,
+ SystemProperties::callChangeCallbacks);
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ /** @hide */
+ public static void reset$ravenwood() {
+ native_reset$ravenwood();
+ synchronized (sChangeCallbacks) {
+ sChangeCallbacks.clear();
+ }
+ }
+
+ // These native methods are currently only implemented by Ravenwood, as it's the only
+ // mechanism we have to jump to our RavenwoodNativeSubstitutionClass
+ private static native void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback);
+ private static native void native_reset$ravenwood();
+
// The one-argument version of native_get used to be a regular native function. Nowadays,
// we use the two-argument form of native_get all the time, but we can't just delete the
// one-argument overload: apps use it via reflection, as the UnsupportedAppUsage annotation
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 5d7e04d4ed26..c0b490929c0a 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -36,6 +36,7 @@ import dalvik.annotation.optimization.FastNative;
* href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
* with Systrace</a>.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class Trace {
/*
* Writes trace events to the kernel trace buffer. These trace events can be
@@ -123,10 +124,26 @@ public final class Trace {
@UnsupportedAppUsage
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long nativeGetEnabledTags();
+ @android.ravenwood.annotation.RavenwoodReplace
private static native void nativeSetAppTracingAllowed(boolean allowed);
+ @android.ravenwood.annotation.RavenwoodReplace
private static native void nativeSetTracingEnabled(boolean allowed);
+ private static long nativeGetEnabledTags$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static void nativeSetAppTracingAllowed$ravenwood(boolean allowed) {
+ // Tracing currently completely disabled under Ravenwood
+ }
+
+ private static void nativeSetTracingEnabled$ravenwood(boolean allowed) {
+ // Tracing currently completely disabled under Ravenwood
+ }
+
@FastNative
private static native void nativeTraceCounter(long tag, String name, long value);
@FastNative
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c280d132ae38..d6df8d940904 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -88,6 +88,7 @@ import java.util.Set;
* See {@link DevicePolicyManager#ACTION_PROVISION_MANAGED_PROFILE} for more on managed profiles.
*/
@SystemService(Context.USER_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class UserManager {
private static final String TAG = "UserManager";
@@ -106,6 +107,21 @@ public class UserManager {
/** Whether the device is in headless system user mode; null until cached. */
private static Boolean sIsHeadlessSystemUser = null;
+ /** Maximum length of username.
+ * @hide
+ */
+ public static final int MAX_USER_NAME_LENGTH = 100;
+
+ /** Maximum length of user property String value.
+ * @hide
+ */
+ public static final int MAX_ACCOUNT_STRING_LENGTH = 500;
+
+ /** Maximum length of account options String values.
+ * @hide
+ */
+ public static final int MAX_ACCOUNT_OPTIONS_LENGTH = 1000;
+
/**
* User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
* This type of user cannot be created; it can only pre-exist on first boot.
@@ -1882,6 +1898,30 @@ public class UserManager {
"no_near_field_communication_radio";
/**
+ * This user restriction specifies if Thread network is disallowed on the device. If Thread
+ * network is disallowed it cannot be turned on via Settings.
+ *
+ * <p>This restriction can only be set by a device owner or a profile owner of an
+ * organization-owned managed profile on the parent profile.
+ * In both cases, the restriction applies globally on the device and will turn off the
+ * Thread network radio if it's currently on and prevent the radio from being turned
+ * on in the future.
+ *
+ * <p> <a href="https://www.threadgroup.org">Thread</a> is a low-power and low-latency wireless
+ * mesh networking protocol built on IPv6.
+ *
+ * <p>Default is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ @FlaggedApi("com.android.net.thread.flags.thread_user_restriction_enabled")
+ public static final String DISALLOW_THREAD_NETWORK = "no_thread_network";
+
+ /**
* List of key values that can be passed into the various user restriction related methods
* in {@link UserManager} & {@link DevicePolicyManager}.
* Note: This is slightly different from the real set of user restrictions listed in {@link
@@ -1967,6 +2007,7 @@ public class UserManager {
DISALLOW_ULTRA_WIDEBAND_RADIO,
DISALLOW_GRANT_ADMIN,
DISALLOW_NEAR_FIELD_COMMUNICATION_RADIO,
+ DISALLOW_THREAD_NETWORK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
@@ -2906,6 +2947,7 @@ public class UserManager {
* {@link UserManager#USER_TYPE_PROFILE_MANAGED managed profile}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeManagedProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_MANAGED.equals(userType);
}
@@ -2914,6 +2956,7 @@ public class UserManager {
* Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_GUEST guest user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeGuest(@Nullable String userType) {
return USER_TYPE_FULL_GUEST.equals(userType);
}
@@ -2923,6 +2966,7 @@ public class UserManager {
* {@link UserManager#USER_TYPE_FULL_RESTRICTED restricted user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeRestricted(@Nullable String userType) {
return USER_TYPE_FULL_RESTRICTED.equals(userType);
}
@@ -2931,6 +2975,7 @@ public class UserManager {
* Returns whether the user type is a {@link UserManager#USER_TYPE_FULL_DEMO demo user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeDemo(@Nullable String userType) {
return USER_TYPE_FULL_DEMO.equals(userType);
}
@@ -2939,6 +2984,7 @@ public class UserManager {
* Returns whether the user type is a {@link UserManager#USER_TYPE_PROFILE_CLONE clone user}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeCloneProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_CLONE.equals(userType);
}
@@ -2948,6 +2994,7 @@ public class UserManager {
* {@link UserManager#USER_TYPE_PROFILE_COMMUNAL communal profile}.
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypeCommunalProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_COMMUNAL.equals(userType);
}
@@ -2958,6 +3005,7 @@ public class UserManager {
*
* @hide
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean isUserTypePrivateProfile(@Nullable String userType) {
return USER_TYPE_PROFILE_PRIVATE.equals(userType);
}
@@ -4423,15 +4471,15 @@ public class UserManager {
* This API should only be called if the current user is an {@link #isAdminUser() admin} user,
* as otherwise the returned intent will not be able to create a user.
*
- * @param userName Optional name to assign to the user.
+ * @param userName Optional name to assign to the user. Character limit is 100.
* @param accountName Optional account name that will be used by the setup wizard to initialize
- * the user.
+ * the user. Character limit is 500.
* @param accountType Optional account type for the account to be created. This is required
- * if the account name is specified.
+ * if the account name is specified. Character limit is 500.
* @param accountOptions Optional bundle of data to be passed in during account creation in the
* new user via {@link AccountManager#addAccount(String, String, String[],
* Bundle, android.app.Activity, android.accounts.AccountManagerCallback,
- * Handler)}.
+ * Handler)}. Character limit is 1000.
* @return An Intent that can be launched from an Activity.
* @see #USER_CREATION_FAILED_NOT_PERMITTED
* @see #USER_CREATION_FAILED_NO_MORE_USERS
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ecd6f22607b6..11edcafecdee 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -444,6 +444,18 @@ public final class Settings {
"android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
/**
+ * Activity Action: Show settings to allow configuration of an accessibility
+ * shortcut belonging to an accessibility feature or features.
+ * <p>
+ * Input: ":settings:show_fragment_args" must contain "targets" denoting the services to edit.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ **/
+ public static final String ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS =
+ "android.settings.ACCESSIBILITY_SHORTCUT_SETTINGS";
+
+ /**
* Activity Action: Show settings to allow configuration of accessibility color and motion.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -4513,10 +4525,11 @@ public final class Settings {
/** @hide */
public static void adjustConfigurationForUser(ContentResolver cr, Configuration outConfig,
int userHandle, boolean updateSettingsIfEmpty) {
+ final float defaultFontScale = getDefaultFontScale(cr, userHandle);
outConfig.fontScale = Settings.System.getFloatForUser(
- cr, FONT_SCALE, DEFAULT_FONT_SCALE, userHandle);
+ cr, FONT_SCALE, defaultFontScale, userHandle);
if (outConfig.fontScale < 0) {
- outConfig.fontScale = DEFAULT_FONT_SCALE;
+ outConfig.fontScale = defaultFontScale;
}
outConfig.fontWeightAdjustment = Settings.Secure.getIntForUser(
cr, Settings.Secure.FONT_WEIGHT_ADJUSTMENT, DEFAULT_FONT_WEIGHT, userHandle);
@@ -4541,6 +4554,12 @@ public final class Settings {
}
}
+ private static float getDefaultFontScale(ContentResolver cr, int userHandle) {
+ return com.android.window.flags.Flags.configurableFontScaleDefault()
+ ? Settings.System.getFloatForUser(cr, DEFAULT_DEVICE_FONT_SCALE,
+ DEFAULT_FONT_SCALE, userHandle) : DEFAULT_FONT_SCALE;
+ }
+
/**
* @hide Erase the fields in the Configuration that should be applied
* by the settings.
@@ -4907,6 +4926,15 @@ public final class Settings {
public static final String FONT_SCALE = "font_scale";
/**
+ * Default scaling factor for fonts for the specific device, float.
+ * The value is read from the {@link R.dimen.def_device_font_scale}
+ * configuration property.
+ *
+ * @hide
+ */
+ public static final String DEFAULT_DEVICE_FONT_SCALE = "device_font_scale";
+
+ /**
* The serialized system locale value.
*
* Do not use this value directory.
@@ -6245,6 +6273,7 @@ public final class Settings {
PRIVATE_SETTINGS.add(CAMERA_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION);
PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR);
+ PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE);
}
/**
@@ -7881,6 +7910,17 @@ public final class Settings {
public static final String ACCESSIBILITY_BOUNCE_KEYS = "accessibility_bounce_keys";
/**
+ * Whether to enable slow keys for Physical Keyboard accessibility.
+ *
+ * If set to non-zero value, any key press on physical keyboard needs to be pressed and
+ * held for the provided threshold duration (in milliseconds) to be registered in the
+ * system.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SLOW_KEYS = "accessibility_slow_keys";
+
+ /**
* Whether to enable sticky keys for Physical Keyboard accessibility.
*
* This is a boolean value that determines if Sticky keys feature is enabled.
@@ -12276,6 +12316,8 @@ public final class Settings {
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
+ CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_SLOW_KEYS);
+ CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_STICKY_KEYS);
CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_HISTORY_ENABLED);
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 5ad2502d1546..298bdb881e9f 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -622,6 +622,15 @@ public abstract class AutofillService extends Service {
new FillCallback(callback, request.getId())));
}
+ @Override
+ public void onConvertCredentialRequest(
+ @NonNull ConvertCredentialRequest convertCredentialRequest,
+ @NonNull IConvertCredentialCallback convertCredentialCallback) {
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onConvertCredentialRequest,
+ AutofillService.this, convertCredentialRequest,
+ new ConvertCredentialCallback(convertCredentialCallback)));
+ }
@Override
public void onFillCredentialRequest(FillRequest request, IFillCallback callback,
@@ -707,7 +716,19 @@ public abstract class AutofillService extends Service {
*/
public void onFillCredentialRequest(@NonNull FillRequest request,
@NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback,
- IAutoFillManagerClient autofillClientCallback) {}
+ @NonNull IAutoFillManagerClient autofillClientCallback) {}
+
+ /**
+ * Called by the Android system to convert a credential manager response to a dataset
+ *
+ * @param convertCredentialRequest the request that has the original credential manager response
+ * @param convertCredentialCallback callback used to notify the result of the request.
+ *
+ * @hide
+ */
+ public void onConvertCredentialRequest(
+ @NonNull ConvertCredentialRequest convertCredentialRequest,
+ @NonNull ConvertCredentialCallback convertCredentialCallback){}
/**
* Called when the user requests the service to save the contents of a screen.
diff --git a/core/java/android/service/autofill/ConvertCredentialCallback.java b/core/java/android/service/autofill/ConvertCredentialCallback.java
new file mode 100644
index 000000000000..a39f01115338
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+
+/**
+ * <p><code>ConvertCredentialCallback</code> handles convertCredentialResponse from Autofill
+ * Service.
+ *
+ * @hide
+ */
+public final class ConvertCredentialCallback {
+
+ private static final String TAG = "ConvertCredentialCallback";
+
+ private final IConvertCredentialCallback mCallback;
+
+ /** @hide */
+ public ConvertCredentialCallback(IConvertCredentialCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Notifies the Android System that a convertCredentialRequest was fulfilled by the service.
+ *
+ * @param convertCredentialResponse the result
+ */
+ public void onSuccess(@NonNull ConvertCredentialResponse convertCredentialResponse) {
+ try {
+ mCallback.onSuccess(convertCredentialResponse);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notifies the Android System that a convert credential request has failed
+ *
+ * @param message the error message
+ */
+ public void onFailure(@Nullable CharSequence message) {
+ try {
+ mCallback.onFailure(message);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.aidl b/core/java/android/service/autofill/ConvertCredentialRequest.aidl
new file mode 100644
index 000000000000..79681e2dbe84
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialRequest.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+parcelable ConvertCredentialRequest; \ No newline at end of file
diff --git a/core/java/android/service/autofill/ConvertCredentialRequest.java b/core/java/android/service/autofill/ConvertCredentialRequest.java
new file mode 100644
index 000000000000..d2d7556f40b7
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialRequest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.credentials.GetCredentialResponse;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+
+/**
+ * This class represents a request to an autofill service to convert the credential manager response
+ * to a dataset.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class ConvertCredentialRequest implements Parcelable {
+ private final @NonNull GetCredentialResponse mGetCredentialResponse;
+ private final @NonNull Bundle mClientState;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ConvertCredentialRequest.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public ConvertCredentialRequest(
+ @NonNull GetCredentialResponse getCredentialResponse,
+ @NonNull Bundle clientState) {
+ this.mGetCredentialResponse = getCredentialResponse;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mGetCredentialResponse);
+ this.mClientState = clientState;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mClientState);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull GetCredentialResponse getGetCredentialResponse() {
+ return mGetCredentialResponse;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Bundle getClientState() {
+ return mClientState;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ConvertCredentialRequest { " +
+ "getCredentialResponse = " + mGetCredentialResponse + ", " +
+ "clientState = " + mClientState +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mGetCredentialResponse, flags);
+ dest.writeBundle(mClientState);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ConvertCredentialRequest(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ GetCredentialResponse getCredentialResponse = (GetCredentialResponse) in.readTypedObject(GetCredentialResponse.CREATOR);
+ Bundle clientState = in.readBundle();
+
+ this.mGetCredentialResponse = getCredentialResponse;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mGetCredentialResponse);
+ this.mClientState = clientState;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mClientState);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ConvertCredentialRequest> CREATOR
+ = new Parcelable.Creator<ConvertCredentialRequest>() {
+ @Override
+ public ConvertCredentialRequest[] newArray(int size) {
+ return new ConvertCredentialRequest[size];
+ }
+
+ @Override
+ public ConvertCredentialRequest createFromParcel(@NonNull Parcel in) {
+ return new ConvertCredentialRequest(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1706132305002L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialRequest.java",
+ inputSignatures = "private final @android.annotation.NonNull android.credentials.GetCredentialResponse mGetCredentialResponse\nprivate final @android.annotation.NonNull android.os.Bundle mClientState\nclass ConvertCredentialRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.aidl b/core/java/android/service/autofill/ConvertCredentialResponse.aidl
new file mode 100644
index 000000000000..98ac6f67c521
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialResponse.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+parcelable ConvertCredentialResponse; \ No newline at end of file
diff --git a/core/java/android/service/autofill/ConvertCredentialResponse.java b/core/java/android/service/autofill/ConvertCredentialResponse.java
new file mode 100644
index 000000000000..5da4f63f31f9
--- /dev/null
+++ b/core/java/android/service/autofill/ConvertCredentialResponse.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Response for a {@Link ConvertCredentialRequest}
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class ConvertCredentialResponse implements Parcelable {
+ private final @NonNull Dataset mDataset;
+ private final @Nullable Bundle mClientState;
+
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new ConvertCredentialResponse.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public ConvertCredentialResponse(
+ @NonNull Dataset dataset,
+ @Nullable Bundle clientState) {
+ this.mDataset = dataset;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDataset);
+ this.mClientState = clientState;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Dataset getDataset() {
+ return mDataset;
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable Bundle getClientState() {
+ return mClientState;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "ConvertCredentialResponse { " +
+ "dataset = " + mDataset + ", " +
+ "clientState = " + mClientState +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mClientState != null) flg |= 0x2;
+ dest.writeByte(flg);
+ dest.writeTypedObject(mDataset, flags);
+ if (mClientState != null) dest.writeBundle(mClientState);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ ConvertCredentialResponse(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ Dataset dataset = (Dataset) in.readTypedObject(Dataset.CREATOR);
+ Bundle clientState = (flg & 0x2) == 0 ? null : in.readBundle();
+
+ this.mDataset = dataset;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mDataset);
+ this.mClientState = clientState;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<ConvertCredentialResponse> CREATOR
+ = new Parcelable.Creator<ConvertCredentialResponse>() {
+ @Override
+ public ConvertCredentialResponse[] newArray(int size) {
+ return new ConvertCredentialResponse[size];
+ }
+
+ @Override
+ public ConvertCredentialResponse createFromParcel(@NonNull Parcel in) {
+ return new ConvertCredentialResponse(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1706132669373L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/ConvertCredentialResponse.java",
+ inputSignatures = "private final @android.annotation.NonNull android.service.autofill.Dataset mDataset\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nclass ConvertCredentialResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index 5d58120ef5bb..98dda1031eff 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -309,12 +309,19 @@ public final class FillEventHistory implements Parcelable {
/** The autofill suggestion is shown as a dialog presentation. */
public static final int UI_TYPE_DIALOG = 3;
+ /**
+ * The autofill suggestion is shown os a credman bottom sheet
+ * @hide
+ */
+ public static final int UI_TYPE_CREDMAN_BOTTOM_SHEET = 4;
+
/** @hide */
@IntDef(prefix = { "UI_TYPE_" }, value = {
UI_TYPE_UNKNOWN,
UI_TYPE_MENU,
UI_TYPE_INLINE,
- UI_TYPE_DIALOG
+ UI_TYPE_DIALOG,
+ UI_TYPE_CREDMAN_BOTTOM_SHEET
})
@Retention(RetentionPolicy.SOURCE)
public @interface UiType {}
@@ -755,6 +762,8 @@ public final class FillEventHistory implements Parcelable {
return "UI_TYPE_INLINE";
case UI_TYPE_DIALOG:
return "UI_TYPE_FILL_DIALOG";
+ case UI_TYPE_CREDMAN_BOTTOM_SHEET:
+ return "UI_TYPE_CREDMAN_BOTTOM_SHEET";
default:
return "UI_TYPE_UNKNOWN";
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 7ea74d375ffb..09ec933880d4 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -28,6 +28,7 @@ import android.annotation.StringRes;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
@@ -116,6 +117,7 @@ public final class FillResponse implements Parcelable {
private final boolean mShowFillDialogIcon;
private final boolean mShowSaveDialogIcon;
private final @Nullable FieldClassification[] mDetectedFieldTypes;
+ private final @Nullable PendingIntent mDialogPendingIntent;
/**
* Creates a shollow copy of the provided FillResponse.
@@ -150,7 +152,8 @@ public final class FillResponse implements Parcelable {
r.mServiceDisplayNameResourceId,
r.mShowFillDialogIcon,
r.mShowSaveDialogIcon,
- r.mDetectedFieldTypes);
+ r.mDetectedFieldTypes,
+ r.mDialogPendingIntent);
}
private FillResponse(ParceledListSlice<Dataset> datasets, SaveInfo saveInfo, Bundle clientState,
@@ -163,7 +166,7 @@ public final class FillResponse implements Parcelable {
int[] cancelIds, boolean supportsInlineSuggestions, int iconResourceId,
int serviceDisplayNameResourceId, boolean showFillDialogIcon,
boolean showSaveDialogIcon,
- FieldClassification[] detectedFieldTypes) {
+ FieldClassification[] detectedFieldTypes, PendingIntent dialogPendingIntent) {
mDatasets = datasets;
mSaveInfo = saveInfo;
mClientState = clientState;
@@ -190,6 +193,7 @@ public final class FillResponse implements Parcelable {
mShowFillDialogIcon = showFillDialogIcon;
mShowSaveDialogIcon = showSaveDialogIcon;
mDetectedFieldTypes = detectedFieldTypes;
+ mDialogPendingIntent = dialogPendingIntent;
}
private FillResponse(@NonNull Builder builder) {
@@ -219,6 +223,7 @@ public final class FillResponse implements Parcelable {
mShowFillDialogIcon = builder.mShowFillDialogIcon;
mShowSaveDialogIcon = builder.mShowSaveDialogIcon;
mDetectedFieldTypes = builder.mDetectedFieldTypes;
+ mDialogPendingIntent = builder.mDialogPendingIntent;
}
/** @hide */
@@ -399,6 +404,7 @@ public final class FillResponse implements Parcelable {
private boolean mShowFillDialogIcon = true;
private boolean mShowSaveDialogIcon = true;
private FieldClassification[] mDetectedFieldTypes;
+ private PendingIntent mDialogPendingIntent;
/**
* Adds a new {@link FieldClassification} to this response, to
@@ -1079,6 +1085,24 @@ public final class FillResponse implements Parcelable {
}
/**
+ * Sets credential dialog pending intent. Framework will use the intent to launch the
+ * selector UI. A replacement for previous fill bottom sheet.
+ *
+ * @throws IllegalStateException if {@link #build()} was already called.
+ * @throws NullPointerException if {@code pendingIntent} is {@code null}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setDialogPendingIntent(@NonNull PendingIntent pendingIntent) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(pendingIntent,
+ "can't pass a null object to setDialogPendingIntent");
+ mDialogPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
@@ -1187,6 +1211,9 @@ public final class FillResponse implements Parcelable {
if (mAuthentication != null) {
builder.append(", hasAuthentication");
}
+ if (mDialogPendingIntent != null) {
+ builder.append(", hasDialogPendingIntent");
+ }
if (mAuthenticationIds != null) {
builder.append(", authenticationIds=").append(Arrays.toString(mAuthenticationIds));
}
@@ -1232,6 +1259,7 @@ public final class FillResponse implements Parcelable {
parcel.writeParcelable(mInlineTooltipPresentation, flags);
parcel.writeParcelable(mDialogPresentation, flags);
parcel.writeParcelable(mDialogHeader, flags);
+ parcel.writeParcelable(mDialogPendingIntent, flags);
parcel.writeParcelableArray(mFillDialogTriggerIds, flags);
parcel.writeParcelable(mHeader, flags);
parcel.writeParcelable(mFooter, flags);
@@ -1282,6 +1310,11 @@ public final class FillResponse implements Parcelable {
if (dialogHeader != null) {
builder.setDialogHeader(dialogHeader);
}
+ final PendingIntent dialogPendingIntent = parcel.readParcelable(null,
+ PendingIntent.class);
+ if (dialogPendingIntent != null) {
+ builder.setDialogPendingIntent(dialogPendingIntent);
+ }
final AutofillId[] triggerIds = parcel.readParcelableArray(null, AutofillId.class);
if (triggerIds != null) {
builder.setFillDialogTriggerIds(triggerIds);
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 03ead3266521..2c2feae7aeea 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -16,6 +16,8 @@
package android.service.autofill;
+import android.service.autofill.ConvertCredentialRequest;
+import android.service.autofill.IConvertCredentialCallback;
import android.service.autofill.FillRequest;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
@@ -32,7 +34,8 @@ oneway interface IAutoFillService {
void onConnectedStateChanged(boolean connected);
void onFillRequest(in FillRequest request, in IFillCallback callback);
void onFillCredentialRequest(in FillRequest request, in IFillCallback callback,
- in IAutoFillManagerClient client);
+ in IAutoFillManagerClient client);
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
void onSavedPasswordCountRequest(in IResultReceiver receiver);
+ void onConvertCredentialRequest(in ConvertCredentialRequest convertCredentialRequest, in IConvertCredentialCallback convertCredentialCallback);
}
diff --git a/core/java/android/service/autofill/IConvertCredentialCallback.aidl b/core/java/android/service/autofill/IConvertCredentialCallback.aidl
new file mode 100644
index 000000000000..9dfc29429876
--- /dev/null
+++ b/core/java/android/service/autofill/IConvertCredentialCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.autofill;
+
+import android.os.ICancellationSignal;
+
+import android.service.autofill.ConvertCredentialResponse;
+
+/**
+ * Interface to receive the result of a convert credential request
+ *
+ * @hide
+ */
+oneway interface IConvertCredentialCallback {
+ void onSuccess(in ConvertCredentialResponse convertCredentialResponse);
+ void onFailure(CharSequence message);
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 9895551a8672..9d19ef6bdc64 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1053,7 +1053,7 @@ public class ZenModeConfig implements Parcelable {
out);
if (Flags.modesApi()) {
- writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out);
+ writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannelsAllowed(), out);
}
}
@@ -1381,7 +1381,7 @@ public class ZenModeConfig implements Parcelable {
int state = defaultPolicy.state;
if (Flags.modesApi()) {
state = Policy.policyState(defaultPolicy.hasPriorityChannels(),
- ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannels(),
+ ZenPolicy.stateToBoolean(zenPolicy.getPriorityChannelsAllowed(),
DEFAULT_ALLOW_PRIORITY_CHANNELS));
}
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index d8318a6bee7c..786d768bc55b 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -570,7 +570,7 @@ public final class ZenPolicy implements Parcelable {
* with {@link NotificationChannel#canBypassDnd()} will be intercepted.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- public @State int getPriorityChannels() {
+ public @State int getPriorityChannelsAllowed() {
switch (mAllowChannels) {
case CHANNEL_POLICY_PRIORITY:
return STATE_ALLOW;
@@ -1529,7 +1529,7 @@ public final class ZenPolicy implements Parcelable {
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM, getPriorityConversationSenders());
if (Flags.modesApi()) {
- proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannels());
+ proto.write(DNDPolicyProto.ALLOW_CHANNELS, getPriorityChannelsAllowed());
}
proto.flush();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 76e0c259b38f..bbda0684f1d8 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -271,7 +271,6 @@ public abstract class WallpaperService extends Service {
boolean mDrawingAllowed;
boolean mOffsetsChanged;
boolean mFixedSizeAllowed;
- boolean mShouldDim;
// Whether the wallpaper should be dimmed by default (when no additional dimming is applied)
// based on its color hints
boolean mShouldDimByDefault;
@@ -348,9 +347,11 @@ public abstract class WallpaperService extends Service {
private Display mDisplay;
private Context mDisplayContext;
private int mDisplayState;
- private float mWallpaperDimAmount = 0.05f;
+
+ private float mCustomDimAmount = 0f;
+ private float mWallpaperDimAmount = 0f;
private float mPreviousWallpaperDimAmount = mWallpaperDimAmount;
- private float mDefaultDimAmount = mWallpaperDimAmount;
+ private float mDefaultDimAmount = 0.05f;
SurfaceControl mSurfaceControl = new SurfaceControl();
SurfaceControl mBbqSurfaceControl;
@@ -986,11 +987,8 @@ public abstract class WallpaperService extends Service {
mShouldDimByDefault = ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
&& (colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) == 0);
- // If default dimming value changes and no additional dimming is applied
- if (mShouldDimByDefault != mShouldDim && mWallpaperDimAmount == 0f) {
- mShouldDim = mShouldDimByDefault;
- updateSurfaceDimming();
- }
+ // Recompute dim in case it changed compared to the previous WallpaperService
+ updateWallpaperDimming(mCustomDimAmount);
}
/**
@@ -999,28 +997,21 @@ public abstract class WallpaperService extends Service {
* @param dimAmount Float amount between [0.0, 1.0] to dim the wallpaper.
*/
private void updateWallpaperDimming(float dimAmount) {
- if (dimAmount == mWallpaperDimAmount) {
- return;
- }
+ mCustomDimAmount = Math.min(1f, dimAmount);
- // Custom dim amount cannot be less than the default dim amount.
- mWallpaperDimAmount = Math.max(mDefaultDimAmount, dimAmount);
- // If dim amount is 0f (additional dimming is removed), then the wallpaper should dim
- // based on its default wallpaper color hints.
- mShouldDim = dimAmount != 0f || mShouldDimByDefault;
- updateSurfaceDimming();
- }
+ // If default dim is enabled, the actual dim amount is at least the default dim amount
+ mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount
+ : Math.max(mDefaultDimAmount, mCustomDimAmount);
- private void updateSurfaceDimming() {
- if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null) {
+ if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null
+ || mWallpaperDimAmount == mPreviousWallpaperDimAmount) {
return;
}
SurfaceControl.Transaction surfaceControlTransaction = new SurfaceControl.Transaction();
// TODO: apply the dimming to preview as well once surface transparency works in
// preview mode.
- if ((!isPreview() && mShouldDim)
- || mPreviousWallpaperDimAmount != mWallpaperDimAmount) {
+ if (!isPreview()) {
Log.v(TAG, "Setting wallpaper dimming: " + mWallpaperDimAmount);
// Animate dimming to gradually change the wallpaper alpha from the previous
@@ -1545,8 +1536,6 @@ public abstract class WallpaperService extends Service {
.createWindowContext(TYPE_WALLPAPER, null /* options */);
mDefaultDimAmount = mDisplayContext.getResources().getFloat(
com.android.internal.R.dimen.config_wallpaperDimAmount);
- mWallpaperDimAmount = mDefaultDimAmount;
- mPreviousWallpaperDimAmount = mWallpaperDimAmount;
mDisplayState = mDisplay.getCommittedState();
mMergedConfiguration.setOverrideConfiguration(
mDisplayContext.getResources().getConfiguration());
diff --git a/core/java/android/util/Singleton.java b/core/java/android/util/Singleton.java
index 92646b47cef2..d27bef9e9adc 100644
--- a/core/java/android/util/Singleton.java
+++ b/core/java/android/util/Singleton.java
@@ -25,6 +25,7 @@ import android.compat.annotation.UnsupportedAppUsage;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public abstract class Singleton<T> {
@UnsupportedAppUsage
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index f28574ecb3b2..27c509a8605f 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -197,9 +197,28 @@ public interface AttachedSurfaceControl {
* Transfer the currently in progress touch gesture from the host to the requested
* {@link SurfaceControlViewHost.SurfacePackage}. This requires that the
* SurfaceControlViewHost was created with the current host's inputToken.
+ * <p>
+ * When the touch is transferred, the window currently receiving touch gets an ACTION_CANCEL
+ * and does not receive any further input events for this gesture.
+ * <p>
+ * The transferred-to window receives an ACTION_DOWN event and then the remainder of the
+ * input events for this gesture. It does not receive any of the previous events of this gesture
+ * that the originating window received.
+ * <p>
+ * The "transferTouch" API only works for the current gesture. When a new gesture arrives,
+ * input dispatcher will do a new round of hit testing. So, if the "host" window is still the
+ * first thing that's being touched, then it will receive the new gesture again. It will
+ * again be up to the host to transfer this new gesture to the embedded.
+ * <p>
+ * Once the transferred-to window receives the gesture, it can choose to give up this gesture
+ * and send it to another window that it's linked to (it can't be an arbitrary window for
+ * security reasons) using the same transferTouch API. Only the window currently receiving
+ * touch is allowed to transfer the gesture.
*
* @param surfacePackage The SurfacePackage to transfer the gesture to.
* @return Whether the touch stream was transferred.
+ * @see SurfaceControlViewHost#transferTouchGestureToHost() for the reverse to transfer touch
+ * gesture from the embedded to the host.
*/
@FlaggedApi(Flags.FLAG_TRANSFER_GESTURE_TO_EMBEDDED)
default boolean transferHostTouchGestureToEmbedded(
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1908c64ce42d..fbadef3d19ef 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2079,6 +2079,7 @@ public final class Display {
*
* @see Display#getSupportedModes()
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class Mode implements Parcelable {
/**
* @hide
@@ -2467,6 +2468,7 @@ public final class Display {
* <p>You can get an instance for a given {@link Display} object with
* {@link Display#getHdrCapabilities getHdrCapabilities()}.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class HdrCapabilities implements Parcelable {
/**
* Invalid luminance value.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 981911ec8880..5654bc159568 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -51,6 +51,7 @@ import java.util.Objects;
* Describes the characteristics of a particular logical display.
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class DisplayInfo implements Parcelable {
/**
* The surface flinger layer stack associated with this logical display.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 1d81be17f580..d2c25cde35d6 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -235,7 +235,7 @@ interface IWindowSession {
*/
oneway void setWallpaperDisplayOffset(IBinder windowToken, int x, int y);
- Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+ oneway void sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, in Bundle extras, boolean sync);
@UnsupportedAppUsage
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 17a3a12d3b79..7f1e037e92d4 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -34,11 +34,11 @@ import static android.view.InsetsController.DEBUG;
import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsController.LayoutInsetsDuringAnimation;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_FLOATING;
-import static android.view.InsetsState.ISIDE_LEFT;
-import static android.view.InsetsState.ISIDE_RIGHT;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_NONE;
+import static android.view.InsetsSource.SIDE_LEFT;
+import static android.view.InsetsSource.SIDE_RIGHT;
+import static android.view.InsetsSource.SIDE_TOP;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -60,7 +60,7 @@ import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseSetArray;
import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsSource.InternalInsetsSide;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
@@ -142,7 +142,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
if (mHasZeroInsetsIme) {
// IME has shownInsets of ZERO, and can't map to a side by default.
// Map zero insets IME to bottom, making it a special case of bottom insets.
- idSideMap.put(ID_IME, ISIDE_BOTTOM);
+ idSideMap.put(ID_IME, SIDE_BOTTOM);
}
buildSideControlsMap(idSideMap, mSideControlsMap, controls);
} else {
@@ -286,10 +286,10 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
}
final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
final ArrayList<SurfaceParams> params = new ArrayList<>();
- updateLeashesForSide(ISIDE_LEFT, offset.left, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_TOP, offset.top, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
- updateLeashesForSide(ISIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_LEFT, offset.left, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_TOP, offset.top, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
+ updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
@@ -499,19 +499,19 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
final float surfaceOffset = mTranslator != null
? mTranslator.translateLengthInAppWindowToScreen(offset) : offset;
switch (side) {
- case ISIDE_LEFT:
+ case SIDE_LEFT:
m.postTranslate(-surfaceOffset, 0);
frame.offset(-offset, 0);
break;
- case ISIDE_TOP:
+ case SIDE_TOP:
m.postTranslate(0, -surfaceOffset);
frame.offset(0, -offset);
break;
- case ISIDE_RIGHT:
+ case SIDE_RIGHT:
m.postTranslate(surfaceOffset, 0);
frame.offset(offset, 0);
break;
- case ISIDE_BOTTOM:
+ case SIDE_BOTTOM:
m.postTranslate(0, surfaceOffset);
frame.offset(0, offset);
break;
@@ -543,9 +543,10 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
// control may be null if it got revoked.
continue;
}
- @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
- if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) {
- side = ISIDE_BOTTOM;
+ @InternalInsetsSide int side = InsetsSource.getInsetSide(control.getInsetsHint());
+ if (side == SIDE_NONE && control.getType() == WindowInsets.Type.ime()) {
+ // IME might not provide insets when it is fullscreen or floating.
+ side = SIDE_BOTTOM;
}
sideControlsMap.add(side, control);
}
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 0927d4519b9c..bc33d5e2f6b1 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.InsetsSourceProto.FRAME;
+import static android.view.InsetsSourceProto.TYPE;
import static android.view.InsetsSourceProto.TYPE_NUMBER;
import static android.view.InsetsSourceProto.VISIBLE;
import static android.view.InsetsSourceProto.VISIBLE_FRAME;
@@ -46,6 +47,24 @@ import java.util.StringJoiner;
*/
public class InsetsSource implements Parcelable {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SIDE_", value = {
+ SIDE_NONE,
+ SIDE_LEFT,
+ SIDE_TOP,
+ SIDE_RIGHT,
+ SIDE_BOTTOM,
+ SIDE_UNKNOWN
+ })
+ public @interface InternalInsetsSide {}
+
+ static final int SIDE_NONE = 0;
+ static final int SIDE_LEFT = 1;
+ static final int SIDE_TOP = 2;
+ static final int SIDE_RIGHT = 3;
+ static final int SIDE_BOTTOM = 4;
+ static final int SIDE_UNKNOWN = 5;
+
/** The insets source ID of IME */
public static final int ID_IME = createId(null, 0, ime());
@@ -101,6 +120,12 @@ public class InsetsSource implements Parcelable {
private boolean mVisible;
+ /**
+ * Used to decide which side of the relative frame should receive insets when the frame fully
+ * covers the relative frame.
+ */
+ private @InternalInsetsSide int mSideHint = SIDE_NONE;
+
private final Rect mTmpFrame = new Rect();
public InsetsSource(int id, @InsetsType int type) {
@@ -119,6 +144,7 @@ public class InsetsSource implements Parcelable {
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
+ mSideHint = other.mSideHint;
}
public void set(InsetsSource other) {
@@ -128,6 +154,7 @@ public class InsetsSource implements Parcelable {
? new Rect(other.mVisibleFrame)
: null;
mFlags = other.mFlags;
+ mSideHint = other.mSideHint;
}
public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -160,6 +187,18 @@ public class InsetsSource implements Parcelable {
return this;
}
+ /**
+ * Updates the side hint which is used to decide which side of the relative frame should receive
+ * insets when the frame fully covers the relative frame.
+ *
+ * @param bounds A rectangle which contains the frame. It will be used to calculate the hint.
+ */
+ public InsetsSource updateSideHint(Rect bounds) {
+ mSideHint = getInsetSide(
+ calculateInsets(bounds, mFrame, true /* ignoreVisibility */));
+ return this;
+ }
+
public int getId() {
return mId;
}
@@ -236,8 +275,21 @@ public class InsetsSource implements Parcelable {
return Insets.of(0, 0, 0, mTmpFrame.height());
}
- // Intersecting at top/bottom
- if (mTmpFrame.width() == relativeFrame.width()) {
+ if (mTmpFrame.equals(relativeFrame)) {
+ // Covering all sides
+ switch (mSideHint) {
+ default:
+ case SIDE_LEFT:
+ return Insets.of(mTmpFrame.width(), 0, 0, 0);
+ case SIDE_TOP:
+ return Insets.of(0, mTmpFrame.height(), 0, 0);
+ case SIDE_RIGHT:
+ return Insets.of(0, 0, mTmpFrame.width(), 0);
+ case SIDE_BOTTOM:
+ return Insets.of(0, 0, 0, mTmpFrame.height());
+ }
+ } else if (mTmpFrame.width() == relativeFrame.width()) {
+ // Intersecting at top/bottom
if (mTmpFrame.top == relativeFrame.top) {
return Insets.of(0, mTmpFrame.height(), 0, 0);
} else if (mTmpFrame.bottom == relativeFrame.bottom) {
@@ -249,9 +301,8 @@ public class InsetsSource implements Parcelable {
if (mTmpFrame.top == 0) {
return Insets.of(0, mTmpFrame.height(), 0, 0);
}
- }
- // Intersecting at left/right
- else if (mTmpFrame.height() == relativeFrame.height()) {
+ } else if (mTmpFrame.height() == relativeFrame.height()) {
+ // Intersecting at left/right
if (mTmpFrame.left == relativeFrame.left) {
return Insets.of(mTmpFrame.width(), 0, 0, 0);
} else if (mTmpFrame.right == relativeFrame.right) {
@@ -283,6 +334,46 @@ public class InsetsSource implements Parcelable {
}
/**
+ * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
+ * is set in order that this method returns a meaningful result.
+ */
+ static @InternalInsetsSide int getInsetSide(Insets insets) {
+ if (Insets.NONE.equals(insets)) {
+ return SIDE_NONE;
+ }
+ if (insets.left != 0) {
+ return SIDE_LEFT;
+ }
+ if (insets.top != 0) {
+ return SIDE_TOP;
+ }
+ if (insets.right != 0) {
+ return SIDE_RIGHT;
+ }
+ if (insets.bottom != 0) {
+ return SIDE_BOTTOM;
+ }
+ return SIDE_UNKNOWN;
+ }
+
+ static String sideToString(@InternalInsetsSide int side) {
+ switch (side) {
+ case SIDE_NONE:
+ return "NONE";
+ case SIDE_LEFT:
+ return "LEFT";
+ case SIDE_TOP:
+ return "TOP";
+ case SIDE_RIGHT:
+ return "RIGHT";
+ case SIDE_BOTTOM:
+ return "BOTTOM";
+ default:
+ return "UNKNOWN:" + side;
+ }
+ }
+
+ /**
* Creates an identifier of an {@link InsetsSource}.
*
* @param owner An object owned by the owner. Only the owner can modify its own sources.
@@ -331,7 +422,7 @@ public class InsetsSource implements Parcelable {
}
public static String flagsToString(@Flags int flags) {
- final StringJoiner joiner = new StringJoiner(" ");
+ final StringJoiner joiner = new StringJoiner("|");
if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
joiner.add("SUPPRESS_SCRIM");
}
@@ -352,6 +443,10 @@ public class InsetsSource implements Parcelable {
*/
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
+ if (!android.os.Flags.androidOsBuildVanillaIceCream()) {
+ // Deprecated since V.
+ proto.write(TYPE, WindowInsets.Type.toString(mType));
+ }
mFrame.dumpDebug(proto, FRAME);
if (mVisibleFrame != null) {
mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
@@ -371,6 +466,7 @@ public class InsetsSource implements Parcelable {
}
pw.print(" visible="); pw.print(mVisible);
pw.print(" flags="); pw.print(flagsToString(mFlags));
+ pw.print(" sideHint="); pw.print(sideToString(mSideHint));
pw.println();
}
@@ -393,6 +489,7 @@ public class InsetsSource implements Parcelable {
if (mType != that.mType) return false;
if (mVisible != that.mVisible) return false;
if (mFlags != that.mFlags) return false;
+ if (mSideHint != that.mSideHint) return false;
if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
return mFrame.equals(that.mFrame);
@@ -400,7 +497,7 @@ public class InsetsSource implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
+ return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint);
}
public InsetsSource(Parcel in) {
@@ -414,6 +511,7 @@ public class InsetsSource implements Parcelable {
}
mVisible = in.readBoolean();
mFlags = in.readInt();
+ mSideHint = in.readInt();
}
@Override
@@ -434,6 +532,7 @@ public class InsetsSource implements Parcelable {
}
dest.writeBoolean(mVisible);
dest.writeInt(mFlags);
+ dest.writeInt(mSideHint);
}
@Override
@@ -442,7 +541,8 @@ public class InsetsSource implements Parcelable {
+ " mType=" + WindowInsets.Type.toString(mType)
+ " mFrame=" + mFrame.toShortString()
+ " mVisible=" + mVisible
- + " mFlags=[" + flagsToString(mFlags) + "]"
+ + " mFlags=" + flagsToString(mFlags)
+ + " mSideHint=" + sideToString(mSideHint)
+ "}";
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 59e0932ecd80..c88da9e2eb4f 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -37,7 +37,6 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration.ActivityType;
@@ -48,6 +47,7 @@ import android.os.Parcelable;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
+import android.view.InsetsSource.InternalInsetsSide;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -55,8 +55,6 @@ import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.StringJoiner;
@@ -66,23 +64,6 @@ import java.util.StringJoiner;
*/
public class InsetsState implements Parcelable {
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "ISIDE", value = {
- ISIDE_LEFT,
- ISIDE_TOP,
- ISIDE_RIGHT,
- ISIDE_BOTTOM,
- ISIDE_FLOATING,
- ISIDE_UNKNOWN
- })
- public @interface InternalInsetsSide {}
- static final int ISIDE_LEFT = 0;
- static final int ISIDE_TOP = 1;
- static final int ISIDE_RIGHT = 2;
- static final int ISIDE_BOTTOM = 3;
- static final int ISIDE_FLOATING = 4;
- static final int ISIDE_UNKNOWN = 5;
-
private final SparseArray<InsetsSource> mSources;
/**
@@ -398,37 +379,14 @@ public class InsetsState implements Parcelable {
}
if (idSideMap != null) {
- @InternalInsetsSide int insetSide = getInsetSide(insets);
- if (insetSide != ISIDE_UNKNOWN) {
+ @InternalInsetsSide int insetSide = InsetsSource.getInsetSide(insets);
+ if (insetSide != InsetsSource.SIDE_UNKNOWN) {
idSideMap.put(source.getId(), insetSide);
}
}
}
/**
- * Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
- * is set in order that this method returns a meaningful result.
- */
- static @InternalInsetsSide int getInsetSide(Insets insets) {
- if (Insets.NONE.equals(insets)) {
- return ISIDE_FLOATING;
- }
- if (insets.left != 0) {
- return ISIDE_LEFT;
- }
- if (insets.top != 0) {
- return ISIDE_TOP;
- }
- if (insets.right != 0) {
- return ISIDE_RIGHT;
- }
- if (insets.bottom != 0) {
- return ISIDE_BOTTOM;
- }
- return ISIDE_UNKNOWN;
- }
-
- /**
* Gets the source mapped from the ID, or creates one if no such mapping has been made.
*/
public InsetsSource getOrCreateSource(int id, int type) {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ba7874eb2d21..a1f44e4c9622 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -1256,13 +1256,13 @@ public class Surface implements Parcelable {
}
private static void registerNativeMemoryUsage() {
- if (Flags.enableSurfaceNativeAllocRegistration()) {
+ if (Flags.enableSurfaceNativeAllocRegistrationRo()) {
VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
}
}
private static void freeNativeMemoryUsage() {
- if (Flags.enableSurfaceNativeAllocRegistration()) {
+ if (Flags.enableSurfaceNativeAllocRegistrationRo()) {
VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3c36227eda0a..7bc832ef9e3f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11991,7 +11991,7 @@ public final class ViewRootImpl implements ViewParent,
Runnable timeoutRunnable = () -> Log.e(mTag,
"Failed to submit the sync transaction after 4s. Likely to ANR "
+ "soon");
- mHandler.postDelayed(timeoutRunnable, 4L * Build.HW_TIMEOUT_MULTIPLIER);
+ mHandler.postDelayed(timeoutRunnable, 4000L * Build.HW_TIMEOUT_MULTIPLIER);
transaction.addTransactionCommittedListener(mSimpleExecutor,
() -> mHandler.removeCallbacks(timeoutRunnable));
surfaceSyncGroup.addTransaction(transaction);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 42355bb17c2d..427d053f754e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -6022,8 +6022,8 @@ public interface WindowManager extends ViewManager {
* This is different from
* {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
* SurfaceControlInputReceiver)} in that the input events are received batched. The caller must
- * invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources when
- * no longer needing to use the {@link SurfaceControlInputReceiver}
+ * invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the
+ * resources when no longer needing to use the {@link SurfaceControlInputReceiver}
*
* @param displayId The display that the SurfaceControl will be placed on. Input will
* only work
@@ -6035,14 +6035,9 @@ public interface WindowManager extends ViewManager {
* @param choreographer The Choreographer used for batching. This should match the rendering
* Choreographer.
* @param receiver The SurfaceControlInputReceiver that will receive the input events
- * @return an {@link IBinder} token that is used to unregister the input receiver via
- * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
- * @see #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
- * SurfaceControlInputReceiver)
*/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
- @NonNull
- default IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ default void registerBatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
throw new UnsupportedOperationException(
@@ -6054,8 +6049,8 @@ public interface WindowManager extends ViewManager {
* receive every input event. This is different than calling @link
* #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
* SurfaceControlInputReceiver)} in that the input events are received unbatched. The caller
- * must invoke {@link #unregisterSurfaceControlInputReceiver(IBinder)} to clean up the resources
- * when no longer needing to use the {@link SurfaceControlInputReceiver}
+ * must invoke {@link #unregisterSurfaceControlInputReceiver(SurfaceControl)} to clean up the
+ * resources when no longer needing to use the {@link SurfaceControlInputReceiver}
*
* @param displayId The display that the SurfaceControl will be placed on. Input will only
* work if SurfaceControl is on that display and that display was
@@ -6066,14 +6061,9 @@ public interface WindowManager extends ViewManager {
* @param surfaceControl The SurfaceControl to register the InputChannel for
* @param looper The looper to use when invoking callbacks.
* @param receiver The SurfaceControlInputReceiver that will receive the input events
- * @return an {@link IBinder} token that is used to unregister the input receiver via
- * {@link #unregisterSurfaceControlInputReceiver(IBinder)}.
- * @see #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
- * SurfaceControlInputReceiver)
**/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
- @NonNull
- default IBinder registerUnbatchedSurfaceControlInputReceiver(int displayId,
+ default void registerUnbatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
throw new UnsupportedOperationException(
@@ -6091,17 +6081,32 @@ public interface WindowManager extends ViewManager {
* {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
* SurfaceControlInputReceiver)}
*
- * @param token The token that was returned via
- * {@link #registerBatchedSurfaceControlInputReceiver(int, IBinder,
- * SurfaceControl,
- * Choreographer, SurfaceControlInputReceiver)} or
- * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder,
- * SurfaceControl,
- * Looper, SurfaceControlInputReceiver)}
+ * @param surfaceControl The SurfaceControl to remove and unregister the input channel for.
*/
@FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
- default void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
+ default void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) {
throw new UnsupportedOperationException(
"unregisterSurfaceControlInputReceiver is not implemented");
}
+
+ /**
+ * Returns the input client token for the {@link SurfaceControl}. This will only return non null
+ * if the SurfaceControl was registered for input via
+ * { #registerBatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Choreographer,
+ * SurfaceControlInputReceiver)} or
+ * {@link #registerUnbatchedSurfaceControlInputReceiver(int, IBinder, SurfaceControl, Looper,
+ * SurfaceControlInputReceiver)}.
+ * <p>
+ * This is helpful for testing to ensure the test waits for the layer to be registered with
+ * SurfaceFlinger and Input before proceeding with the test.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SURFACE_CONTROL_INPUT_RECEIVER)
+ @TestApi
+ @Nullable
+ default IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
+ throw new UnsupportedOperationException(
+ "getSurfaceControlInputClientToken is not implemented");
+ }
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 8d40f9a4f7b1..c49fce5b558f 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -38,10 +38,12 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseArray;
import android.view.inputmethod.InputMethodManager;
import android.window.ITrustedPresentationListener;
import android.window.TrustedPresentationThresholds;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
@@ -50,7 +52,6 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
@@ -156,8 +157,9 @@ public final class WindowManagerGlobal {
private final TrustedPresentationListener mTrustedPresentationListener =
new TrustedPresentationListener();
- private final ConcurrentHashMap<IBinder, InputEventReceiver> mSurfaceControlInputReceivers =
- new ConcurrentHashMap<>();
+ @GuardedBy("mSurfaceControlInputReceivers")
+ private final SparseArray<SurfaceControlInputReceiverInfo>
+ mSurfaceControlInputReceivers = new SparseArray<>();
private WindowManagerGlobal() {
}
@@ -816,7 +818,7 @@ public final class WindowManagerGlobal {
mTrustedPresentationListener.removeListener(listener);
}
- IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ void registerBatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
IBinder clientToken = new Binder();
@@ -830,19 +832,21 @@ public final class WindowManagerGlobal {
e.rethrowAsRuntimeException();
}
- mSurfaceControlInputReceivers.put(clientToken,
- new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(),
- choreographer) {
- @Override
- public void onInputEvent(InputEvent event) {
- boolean handled = receiver.onInputEvent(event);
- finishInputEvent(event, handled);
- }
- });
- return clientToken;
+ synchronized (mSurfaceControlInputReceivers) {
+ mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(),
+ new SurfaceControlInputReceiverInfo(clientToken,
+ new BatchedInputEventReceiver(inputChannel, choreographer.getLooper(),
+ choreographer) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ }));
+ }
}
- IBinder registerUnbatchedSurfaceControlInputReceiver(
+ void registerUnbatchedSurfaceControlInputReceiver(
int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
IBinder clientToken = new Binder();
@@ -856,32 +860,53 @@ public final class WindowManagerGlobal {
e.rethrowAsRuntimeException();
}
- mSurfaceControlInputReceivers.put(clientToken,
- new InputEventReceiver(inputChannel, looper) {
- @Override
- public void onInputEvent(InputEvent event) {
- boolean handled = receiver.onInputEvent(event);
- finishInputEvent(event, handled);
- }
- });
-
- return clientToken;
+ synchronized (mSurfaceControlInputReceivers) {
+ mSurfaceControlInputReceivers.put(surfaceControl.getLayerId(),
+ new SurfaceControlInputReceiverInfo(clientToken,
+ new InputEventReceiver(inputChannel, looper) {
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = receiver.onInputEvent(event);
+ finishInputEvent(event, handled);
+ }
+ }));
+ }
}
- void unregisterSurfaceControlInputReceiver(IBinder token) {
- InputEventReceiver inputEventReceiver = mSurfaceControlInputReceivers.get(token);
- if (inputEventReceiver == null) {
- Log.w(TAG, "No registered input event receiver with token: " + token);
+ void unregisterSurfaceControlInputReceiver(SurfaceControl surfaceControl) {
+ SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo;
+ synchronized (mSurfaceControlInputReceivers) {
+ surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.removeReturnOld(
+ surfaceControl.getLayerId());
+ }
+
+ if (surfaceControlInputReceiverInfo == null) {
+ Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl);
return;
}
try {
- WindowManagerGlobal.getWindowSession().remove(token);
+ WindowManagerGlobal.getWindowSession().remove(
+ surfaceControlInputReceiverInfo.mClientToken);
} catch (RemoteException e) {
Log.e(TAG, "Failed to remove input channel", e);
e.rethrowAsRuntimeException();
}
- inputEventReceiver.dispose();
+ surfaceControlInputReceiverInfo.mInputEventReceiver.dispose();
+ }
+
+ IBinder getSurfaceControlInputClientToken(SurfaceControl surfaceControl) {
+ SurfaceControlInputReceiverInfo surfaceControlInputReceiverInfo;
+ synchronized (mSurfaceControlInputReceivers) {
+ surfaceControlInputReceiverInfo = mSurfaceControlInputReceivers.get(
+ surfaceControl.getLayerId());
+ }
+
+ if (surfaceControlInputReceiverInfo == null) {
+ Log.w(TAG, "No registered input event receiver with sc: " + surfaceControl);
+ return null;
+ }
+ return surfaceControlInputReceiverInfo.mClientToken;
}
private final class TrustedPresentationListener extends
@@ -976,6 +1001,17 @@ public final class WindowManagerGlobal {
throw e.rethrowFromSystemServer();
}
}
+
+ private static class SurfaceControlInputReceiverInfo {
+ final IBinder mClientToken;
+ final InputEventReceiver mInputEventReceiver;
+
+ private SurfaceControlInputReceiverInfo(IBinder clientToken,
+ InputEventReceiver inputEventReceiver) {
+ mClientToken = clientToken;
+ mInputEventReceiver = inputEventReceiver;
+ }
+ }
}
final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index aaf5fcc6f095..41d181c1b10c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -523,26 +523,30 @@ public final class WindowManagerImpl implements WindowManager {
mGlobal.unregisterTrustedPresentationListener(listener);
}
- @NonNull
@Override
- public IBinder registerBatchedSurfaceControlInputReceiver(int displayId,
+ public void registerBatchedSurfaceControlInputReceiver(int displayId,
@NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Choreographer choreographer, @NonNull SurfaceControlInputReceiver receiver) {
- return mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ mGlobal.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
surfaceControl, choreographer, receiver);
}
- @NonNull
@Override
- public IBinder registerUnbatchedSurfaceControlInputReceiver(
+ public void registerUnbatchedSurfaceControlInputReceiver(
int displayId, @NonNull IBinder hostToken, @NonNull SurfaceControl surfaceControl,
@NonNull Looper looper, @NonNull SurfaceControlInputReceiver receiver) {
- return mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
+ mGlobal.registerUnbatchedSurfaceControlInputReceiver(displayId, hostToken,
surfaceControl, looper, receiver);
}
@Override
- public void unregisterSurfaceControlInputReceiver(@NonNull IBinder token) {
- mGlobal.unregisterSurfaceControlInputReceiver(token);
+ public void unregisterSurfaceControlInputReceiver(@NonNull SurfaceControl surfaceControl) {
+ mGlobal.unregisterSurfaceControlInputReceiver(surfaceControl);
+ }
+
+ @Override
+ @Nullable
+ public IBinder getSurfaceControlInputClientToken(@NonNull SurfaceControl surfaceControl) {
+ return mGlobal.getSurfaceControlInputClientToken(surfaceControl);
}
}
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index b95e4595d6b9..c4d18c6a8da5 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -534,9 +534,8 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public android.os.Bundle sendWallpaperCommand(android.os.IBinder window,
+ public void sendWallpaperCommand(android.os.IBinder window,
java.lang.String action, int x, int y, int z, android.os.Bundle extras, boolean sync) {
- return null;
}
@Override
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index dbeffc89fa09..559ccfea72c2 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -298,6 +298,15 @@ public final class AutofillManager {
"android.view.autofill.extra.AUGMENTED_AUTOFILL_CLIENT";
/**
+ * Internal extra used to pass the fill request id in client state of
+ * {@link ConvertCredentialResponse}
+ *
+ * @hide
+ */
+ public static final String EXTRA_AUTOFILL_REQUEST_ID =
+ "android.view.autofill.extra.AUTOFILL_REQUEST_ID";
+
+ /**
* Autofill Hint to indicate that it can match any field.
*
* @hide
diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index 37c6f5bf3425..898947adcd1b 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -4,6 +4,7 @@ simranjit@google.com
haoranzhang@google.com
skxu@google.com
yunicorn@google.com
+reemabajwa@google.com
# Bug component: 543785 = per-file *Augmented*
per-file *Augmented* = wangqi@google.com
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
index bf1d31c8496d..fbb66d1485dd 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSessionV2.java
@@ -149,9 +149,12 @@ public final class MainContentCaptureSessionV2 extends ContentCaptureSession {
*
* Because it is not guaranteed that the events will be enqueued from a single thread, the
* implementation must be thread-safe to prevent unexpected behaviour.
+ *
+ * @hide
*/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
@NonNull
- private final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
+ public final ConcurrentLinkedQueue<ContentCaptureEvent> mEventProcessQueue;
/**
* List of events held to be sent to the {@link ContentCaptureService} as a batch.
@@ -908,7 +911,7 @@ public final class MainContentCaptureSessionV2 extends ContentCaptureSession {
* clear the buffer events then starting sending out current event.
*/
private void enqueueEvent(@NonNull final ContentCaptureEvent event, boolean forceFlush) {
- if (forceFlush) {
+ if (forceFlush || mEventProcessQueue.size() >= mManager.mOptions.maxBufferSize - 1) {
// The buffer events are cleared in the same thread first to prevent new events
// being added during the time of context switch. This would disrupt the sequence
// of events.
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
index 9f9b7b4b68a9..1dd99baf8d2a 100644
--- a/core/java/android/view/flags/view_flags.aconfig
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -8,6 +8,15 @@ flag {
}
flag {
+ name: "enable_surface_native_alloc_registration_ro"
+ namespace: "toolkit"
+ description: "Feature flag for registering surfaces with the VM for faster"
+ " cleanup. Fixed readonly version."
+ bug: "306193257"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_use_measure_cache_during_force_layout"
namespace: "toolkit"
description: "Enables using the measure cache during a view force layout from the second "
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index befb0023ebe6..544642811a39 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -30,6 +30,8 @@ import android.os.Parcelable;
import android.util.Log;
import android.view.SurfaceControl;
+import com.android.window.flags.Flags;
+
import libcore.util.NativeAllocationRegistry;
import java.util.concurrent.CountDownLatch;
@@ -48,7 +50,7 @@ public class ScreenCapture {
private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
long captureListener);
private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
- long captureListener);
+ long captureListener, boolean sync);
private static native long nativeCreateScreenCaptureListener(
ObjIntConsumer<ScreenshotHardwareBuffer> consumer);
private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
@@ -134,7 +136,8 @@ public class ScreenCapture {
*/
public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) {
SynchronousScreenCaptureListener syncScreenCapture = createSyncCaptureListener();
- int status = captureLayers(captureArgs, syncScreenCapture);
+ int status = nativeCaptureLayers(captureArgs, syncScreenCapture.mNativeObject,
+ Flags.syncScreenCapture());
if (status != 0) {
return null;
}
@@ -171,7 +174,7 @@ public class ScreenCapture {
*/
public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
- return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
+ return nativeCaptureLayers(captureArgs, captureListener.mNativeObject, false /* sync */);
}
/**
@@ -674,7 +677,7 @@ public class ScreenCapture {
* This listener can only be used for a single call to capture content call.
*/
public static class ScreenCaptureListener implements Parcelable {
- private final long mNativeObject;
+ final long mNativeObject;
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 5dbf328b6c0a..93297e64c621 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -94,11 +94,18 @@ public final class TaskFragmentCreationParams implements Parcelable {
@Nullable
private final IBinder mPairedActivityToken;
+ /**
+ * If {@code true}, transitions are allowed even if the TaskFragment is empty. If
+ * {@code false}, transitions will wait until the TaskFragment becomes non-empty or other
+ * conditions are met. Default to {@code false}.
+ */
+ private final boolean mAllowTransitionWhenEmpty;
+
private TaskFragmentCreationParams(
@NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect initialRelativeBounds,
@WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
- @Nullable IBinder pairedActivityToken) {
+ @Nullable IBinder pairedActivityToken, boolean allowTransitionWhenEmpty) {
if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+ " pairedActivityToken should not be set at the same time.");
@@ -110,6 +117,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mWindowingMode = windowingMode;
mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
mPairedActivityToken = pairedActivityToken;
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
}
@NonNull
@@ -155,6 +163,11 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mPairedActivityToken;
}
+ /** @hide */
+ public boolean getAllowTransitionWhenEmpty() {
+ return mAllowTransitionWhenEmpty;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
@@ -163,6 +176,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
mWindowingMode = in.readInt();
mPairedPrimaryFragmentToken = in.readStrongBinder();
mPairedActivityToken = in.readStrongBinder();
+ mAllowTransitionWhenEmpty = in.readBoolean();
}
/** @hide */
@@ -175,6 +189,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
dest.writeInt(mWindowingMode);
dest.writeStrongBinder(mPairedPrimaryFragmentToken);
dest.writeStrongBinder(mPairedActivityToken);
+ dest.writeBoolean(mAllowTransitionWhenEmpty);
}
@NonNull
@@ -201,6 +216,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " windowingMode=" + mWindowingMode
+ " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ " pairedActivityToken=" + mPairedActivityToken
+ + " allowTransitionWhenEmpty=" + mAllowTransitionWhenEmpty
+ "}";
}
@@ -234,6 +250,8 @@ public final class TaskFragmentCreationParams implements Parcelable {
@Nullable
private IBinder mPairedActivityToken;
+ private boolean mAllowTransitionWhenEmpty;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -298,12 +316,26 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this;
}
+ /**
+ * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+ * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+ * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+ * to {@code false}.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
mInitialRelativeBounds, mWindowingMode, mPairedPrimaryFragmentToken,
- mPairedActivityToken);
+ mPairedActivityToken, mAllowTransitionWhenEmpty);
}
}
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 86804c6117c7..65075aea7b27 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -226,9 +226,6 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
setTopOnBackInvokedCallback(null);
}
- // We should also stop running animations since all callbacks have been removed.
- // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
- Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
}
@@ -442,8 +439,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
return WindowOnBackInvokedDispatcher
.isOnBackInvokedCallbackEnabled(activityInfo, applicationInfo,
- () -> originalContext.obtainStyledAttributes(
- new int[] {android.R.attr.windowSwipeToDismiss}), true);
+ () -> originalContext);
}
@Override
@@ -501,7 +497,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
*/
public static boolean isOnBackInvokedCallbackEnabled(@Nullable ActivityInfo activityInfo,
@NonNull ApplicationInfo applicationInfo,
- @NonNull Supplier<TypedArray> windowAttrSupplier, boolean recycleTypedArray) {
+ @NonNull Supplier<Context> contextSupplier) {
// new back is enabled if the feature flag is enabled AND the app does not explicitly
// request legacy back.
if (!ENABLE_PREDICTIVE_BACK) {
@@ -547,15 +543,15 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
// setTrigger(true)
// Use the original context to resolve the styled attribute so that they stay
// true to the window.
- TypedArray windowAttr = windowAttrSupplier.get();
+ final Context context = contextSupplier.get();
boolean windowSwipeToDismiss = true;
- if (windowAttr != null) {
- if (windowAttr.getIndexCount() > 0) {
- windowSwipeToDismiss = windowAttr.getBoolean(0, true);
- }
- if (recycleTypedArray) {
- windowAttr.recycle();
+ if (context != null) {
+ final TypedArray array = context.obtainStyledAttributes(
+ new int[]{android.R.attr.windowSwipeToDismiss});
+ if (array.getIndexCount() > 0) {
+ windowSwipeToDismiss = array.getBoolean(0, true);
}
+ array.recycle();
}
if (DEBUG) {
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
index cbf6367b3d60..1de77f6d29e7 100644
--- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -50,3 +50,19 @@ flag {
description: "Whether we should allow hiding the size compat restart button"
bug: "318840081"
}
+
+flag {
+ name: "configurable_font_scale_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the font_scale is read from a device dependent configuration file"
+ bug: "319808237"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "camera_compat_for_freeform"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode"
+ bug: "314952133"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 751c1a8dd0ff..069affb4c06c 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -88,3 +88,11 @@ flag {
is_fixed_read_only: true
bug: "304574518"
}
+
+flag {
+ namespace: "window_surfaces"
+ name: "sync_screen_capture"
+ description: "Create a screen capture API that blocks in SurfaceFlinger"
+ is_fixed_read_only: true
+ bug: "321263247"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 2c5fbd7f3725..f234637a7d82 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -38,14 +38,6 @@ flag {
}
flag {
- name: "draw_magnifier_border_outside_wmlock"
- namespace: "windowing_frontend"
- description: "Avoid holding WM locks for a long time when executing lockCanvas"
- bug: "316075123"
- is_fixed_read_only: true
-}
-
-flag {
name: "introduce_smoother_dimmer"
namespace: "windowing_frontend"
description: "Refactor dim to fix flickers"
diff --git a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
index 0a28997885ff..b4e87498f09b 100644
--- a/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
+++ b/core/java/com/android/internal/app/ConfirmUserCreationActivity.java
@@ -116,6 +116,14 @@ public class ConfirmUserCreationActivity extends AlertActivity
if (cantCreateUser) {
setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
return null;
+ } else if (!(isUserPropertyWithinLimit(mUserName, UserManager.MAX_USER_NAME_LENGTH)
+ && isUserPropertyWithinLimit(mAccountName, UserManager.MAX_ACCOUNT_STRING_LENGTH)
+ && isUserPropertyWithinLimit(mAccountType, UserManager.MAX_ACCOUNT_STRING_LENGTH))
+ || (mAccountOptions != null && !mAccountOptions.isBundleContentsWithinLengthLimit(
+ UserManager.MAX_ACCOUNT_OPTIONS_LENGTH))) {
+ setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
+ Log.i(TAG, "User properties must not exceed their character limits");
+ return null;
} else if (cantCreateAnyMoreUsers) {
setResult(UserManager.USER_CREATION_FAILED_NO_MORE_USERS);
return null;
@@ -144,4 +152,8 @@ public class ConfirmUserCreationActivity extends AlertActivity
}
finish();
}
+
+ private boolean isUserPropertyWithinLimit(String property, int limit) {
+ return property == null || property.length() <= limit;
+ }
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index eeea17bf39dd..90ca95a7fbab 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -71,7 +71,7 @@ public class SystemUiSystemPropertiesFlags {
"persist.debug.sysui.notification.notif_cooldown_t1", 60000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
- "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
+ "persist.debug.sysui.notification.notif_cooldown_t2", 10000);
/** Value used by polite notif. feature */
public static final Flag NOTIF_VOLUME1 = devFlag(
"persist.debug.sysui.notification.notif_volume1", 30);
@@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags {
public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
"persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
+ /** Value used by polite notif. feature */
+ public static final Flag NOTIF_AVALANCHE_TIMEOUT = devFlag(
+ "persist.debug.sysui.notification.notif_avalanche_timeout", 120_000);
+
/** b/303716154: For debugging only: use short bitmap duration. */
public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
"persist.sysui.notification.debug_short_bitmap_duration");
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 37aaa72cb7a0..006849034fbd 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -47,6 +47,7 @@ import java.io.PrintWriter;
* (new) system for storing the brightness. It has methods to convert between the two and also
* observes for when one of the settings is changed and syncs this with the other.
*/
+@android.ravenwood.annotation.RavenwoodKeepPartialClass
public class BrightnessSynchronizer {
private static final String TAG = "BrightnessSynchronizer";
@@ -282,6 +283,7 @@ public class BrightnessSynchronizer {
* @param b second float to compare
* @return whether the two values are within a small enough tolerance value
*/
+ @android.ravenwood.annotation.RavenwoodKeep
public static boolean floatEquals(float a, float b) {
if (a == b) {
return true;
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 96740c59ec06..7b3565bff533 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -121,10 +121,11 @@ public class Cuj {
public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87;
+ public static final int CUJ_BACK_PANEL_ARROW = 88;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
@VisibleForTesting
- static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_OPEN;
+ static final int LAST_CUJ = CUJ_BACK_PANEL_ARROW;
/** @hide */
@IntDef({
@@ -207,6 +208,7 @@ public class Cuj {
CUJ_PREDICTIVE_BACK_CROSS_TASK,
CUJ_PREDICTIVE_BACK_HOME,
CUJ_LAUNCHER_SEARCH_QSB_OPEN,
+ CUJ_BACK_PANEL_ARROW,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -298,8 +300,8 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
- CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] =
- FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
}
private Cuj() {
@@ -474,6 +476,8 @@ public class Cuj {
return "PREDICTIVE_BACK_HOME";
case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
return "LAUNCHER_SEARCH_QSB_OPEN";
+ case CUJ_BACK_PANEL_ARROW:
+ return "BACK_PANEL_ARROW";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
index f3f16a0c662d..d9cac12c3372 100644
--- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
+++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java
@@ -28,6 +28,7 @@ import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.WindowCallbacks;
@@ -52,6 +53,7 @@ import com.android.internal.jank.FrameTracker.Reasons;
* @hide
*/
class InteractionMonitorDebugOverlay implements WindowCallbacks {
+ private static final String TAG = "InteractionMonitorDebug";
private static final int REASON_STILL_RUNNING = -1000;
private final Object mLock;
// Sparse array where the key in the CUJ and the value is the session status, or null if
@@ -77,7 +79,7 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks {
mDebugPaint.setAntiAlias(false);
mDebugFontMetrics = new Paint.FontMetrics();
final Context context = ActivityThread.currentApplication();
- mPackageName = context.getPackageName();
+ mPackageName = context == null ? "null" : context.getPackageName();
}
@UiThread
@@ -153,8 +155,14 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks {
SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) {
synchronized (mLock) {
mRunningCujs.put(removedCuj, reason);
+ boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+ if (isLoggable) {
+ String cujName = Cuj.getNameOfCuj(removedCuj);
+ Log.d(TAG, cujName + (reason == REASON_END_NORMAL ? " ended" : " cancelled"));
+ }
// If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended
if (mRunningCujs.indexOfValue(REASON_STILL_RUNNING) < 0) {
+ if (isLoggable) Log.d(TAG, "All CUJs ended");
mRunningCujs.clear();
dispose();
} else {
@@ -186,6 +194,10 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks {
@UiThread
void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ String cujName = Cuj.getNameOfCuj(addedCuj);
+ Log.d(TAG, cujName + " started");
+ }
synchronized (mLock) {
// Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ
// is still running
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index e58f4f06daea..88aa89aaf48b 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -34,6 +34,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class MetricsLogger {
// define metric categories in frameworks/base/proto/src/metrics_constants.proto.
// mirror changes in native version at system/core/libmetricslogger/metrics_logger.cpp
diff --git a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
index 6786427b5d86..df8bf313d06f 100644
--- a/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
+++ b/core/java/com/android/internal/logging/testing/FakeMetricsLogger.java
@@ -12,6 +12,7 @@ import java.util.Queue;
*
* @hide.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class FakeMetricsLogger extends MetricsLogger {
private Queue<LogMaker> logs = new LinkedList<>();
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index e303890c245a..6787ddc5d64d 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -27,6 +27,7 @@ import java.util.List;
*
* @hide.
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class UiEventLoggerFake implements UiEventLogger {
/**
* Immutable data class used to record fake log events.
diff --git a/core/java/com/android/internal/policy/SystemBarUtils.java b/core/java/com/android/internal/policy/SystemBarUtils.java
index 7a1ac071a625..efa369715373 100644
--- a/core/java/com/android/internal/policy/SystemBarUtils.java
+++ b/core/java/com/android/internal/policy/SystemBarUtils.java
@@ -19,8 +19,9 @@ package com.android.internal.policy;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Insets;
-import android.util.RotationUtils;
+import android.view.Display;
import android.view.DisplayCutout;
+import android.view.DisplayInfo;
import android.view.Surface;
import com.android.internal.R;
@@ -56,21 +57,21 @@ public final class SystemBarUtils {
*/
public static int getStatusBarHeightForRotation(
Context context, @Surface.Rotation int targetRot) {
- final int rotation = context.getDisplay().getRotation();
- final DisplayCutout cutout = context.getDisplay().getCutout();
-
- Insets insets = cutout == null ? Insets.NONE : Insets.of(cutout.getSafeInsets());
- Insets waterfallInsets = cutout == null ? Insets.NONE : cutout.getWaterfallInsets();
- // rotate insets to target rotation if needed.
- if (rotation != targetRot) {
- if (!insets.equals(Insets.NONE)) {
- insets = RotationUtils.rotateInsets(
- insets, RotationUtils.deltaRotation(rotation, targetRot));
- }
- if (!waterfallInsets.equals(Insets.NONE)) {
- waterfallInsets = RotationUtils.rotateInsets(
- waterfallInsets, RotationUtils.deltaRotation(rotation, targetRot));
- }
+ final Display display = context.getDisplay();
+ final int rotation = display.getRotation();
+ final DisplayCutout cutout = display.getCutout();
+ DisplayInfo info = new DisplayInfo();
+ display.getDisplayInfo(info);
+ Insets insets;
+ Insets waterfallInsets;
+ if (cutout == null) {
+ insets = Insets.NONE;
+ waterfallInsets = Insets.NONE;
+ } else {
+ DisplayCutout rotated =
+ cutout.getRotated(info.logicalWidth, info.logicalHeight, rotation, targetRot);
+ insets = Insets.of(rotated.getSafeInsets());
+ waterfallInsets = rotated.getWaterfallInsets();
}
final int defaultSize =
context.getResources().getDimensionPixelSize(R.dimen.status_bar_height_default);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 42be784d8baa..a8d0d37f78bd 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -105,6 +105,9 @@ public class ConversationLayout extends FrameLayout
private int mConversationIconTopPaddingExpandedGroup;
private int mConversationIconTopPadding;
private int mExpandedGroupMessagePadding;
+ // TODO (b/217799515) Currently, mConversationText shows the conversation title, the actual
+ // conversation text is inside of mMessagingLinearLayout, which is misleading, we should rename
+ // this to mConversationTitleView
private TextView mConversationText;
private View mConversationIconBadge;
private CachingIconView mConversationIconBadgeBg;
@@ -125,6 +128,11 @@ public class ConversationLayout extends FrameLayout
private int mNotificationBackgroundColor;
private CharSequence mFallbackChatName;
private CharSequence mFallbackGroupChatName;
+ //TODO (b/217799515) Currently, Notification.MessagingStyle, ConversationLayout, and
+ // HybridConversationNotificationView, each has their own definition of "ConversationTitle".
+ // What make things worse is that the term of "ConversationTitle" often confuses with
+ // "ConversationText".
+ // We need to unify them or differentiate the namings.
private CharSequence mConversationTitle;
private int mMessageSpacingStandard;
private int mMessageSpacingGroup;
@@ -160,12 +168,12 @@ public class ConversationLayout extends FrameLayout
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr) {
+ @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
- @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@@ -297,13 +305,17 @@ public class ConversationLayout extends FrameLayout
mNameReplacement = nameReplacement;
}
- /** Sets this conversation as "important", adding some additional UI treatment. */
+ /**
+ * Sets this conversation as "important", adding some additional UI treatment.
+ */
@RemotableViewMethod
public void setIsImportantConversation(boolean isImportantConversation) {
setIsImportantConversation(isImportantConversation, false);
}
- /** @hide **/
+ /**
+ * @hide
+ **/
public void setIsImportantConversation(boolean isImportantConversation, boolean animate) {
mImportantConversation = isImportantConversation;
mImportanceRingView.setVisibility(isImportantConversation && mIcon.getVisibility() != GONE
@@ -386,6 +398,7 @@ public class ConversationLayout extends FrameLayout
/**
* Set conversation data
+ *
* @param extras Bundle contains conversation data
*/
@RemotableViewMethod(asyncImpl = "setDataAsync")
@@ -427,6 +440,7 @@ public class ConversationLayout extends FrameLayout
* RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
* This should be called on a background thread, and returns a Runnable which is then must be
* called on the main thread to complete the operation and set text.
+ *
* @param extras Bundle contains conversation data
* @hide
*/
@@ -449,6 +463,7 @@ public class ConversationLayout extends FrameLayout
/**
* enable/disable precomputed text usage
+ *
* @hide
*/
public void setPrecomputedTextEnabled(boolean precomputedTextEnabled) {
@@ -466,7 +481,9 @@ public class ConversationLayout extends FrameLayout
mImageResolver = resolver;
}
- /** @hide */
+ /**
+ * @hide
+ */
public void setUnreadCount(int unreadCount) {
mExpandButton.setNumber(unreadCount);
}
@@ -795,6 +812,10 @@ public class ConversationLayout extends FrameLayout
mConversationTitle = conversationTitle != null ? conversationTitle.toString() : null;
}
+ // TODO (b/217799515) getConversationTitle is not consistent with setConversationTitle
+ // if you call getConversationTitle() immediately after setConversationTitle(), the result
+ // will not correctly reflect the new change without calling updateConversationLayout, for
+ // example.
public CharSequence getConversationTitle() {
return mConversationText.getText();
}
@@ -914,7 +935,7 @@ public class ConversationLayout extends FrameLayout
}
private void createGroupViews(List<List<MessagingMessage>> groups,
- List<Person> senders, boolean showSpinner) {
+ List<Person> senders, boolean showSpinner) {
mGroups.clear();
for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
List<MessagingMessage> group = groups.get(groupIndex);
@@ -963,8 +984,8 @@ public class ConversationLayout extends FrameLayout
}
private void findGroups(List<MessagingMessage> historicMessages,
- List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
- List<Person> senders) {
+ List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+ List<Person> senders) {
CharSequence currentSenderKey = null;
List<MessagingMessage> currentGroup = null;
int histSize = historicMessages.size();
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 0704cb8094d7..5da64350619c 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -18,9 +18,11 @@ package com.android.internal.widget;
import android.annotation.Nullable;
import android.content.Context;
+import android.os.Build;
import android.os.Trace;
import android.text.BoringLayout;
import android.text.Layout;
+import android.text.PrecomputedText;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.method.TransformationMethod;
@@ -48,6 +50,10 @@ public class ImageFloatingTextView extends TextView {
private int mLayoutMaxLines = -1;
private int mImageEndMargin;
+ private int mStaticLayoutCreationCountInOnMeasure = 0;
+
+ private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
public ImageFloatingTextView(Context context) {
this(context, null);
}
@@ -71,7 +77,10 @@ public class ImageFloatingTextView extends TextView {
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize,
TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
- Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("ImageFloatingTextView#makeSingleLayout");
+ mStaticLayoutCreationCountInOnMeasure++;
+ }
TransformationMethod transformationMethod = getTransformationMethod();
CharSequence text = getText();
if (transformationMethod != null) {
@@ -79,7 +88,7 @@ public class ImageFloatingTextView extends TextView {
}
text = text == null ? "" : text;
StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
- getPaint(), wantWidth)
+ getPaint(), wantWidth)
.setAlignment(alignment)
.setTextDirection(getTextDirectionHeuristic())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
@@ -115,7 +124,10 @@ public class ImageFloatingTextView extends TextView {
}
final StaticLayout result = builder.build();
- Trace.endSection();
+ if (TRACE_ONMEASURE) {
+ trackMaxLines();
+ Trace.endSection();
+ }
return result;
}
@@ -141,7 +153,10 @@ public class ImageFloatingTextView extends TextView {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- Trace.beginSection("ImageFloatingTextView#onMeasure");
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("ImageFloatingTextView#onMeasure");
+ }
+ mStaticLayoutCreationCountInOnMeasure = 0;
int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - mPaddingTop - mPaddingBottom;
if (getLayout() != null && getLayout().getHeight() != availableHeight) {
// We've been measured before and the new size is different than before, lets make sure
@@ -168,7 +183,12 @@ public class ImageFloatingTextView extends TextView {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
- Trace.endSection();
+
+
+ if (TRACE_ONMEASURE) {
+ trackParameters();
+ Trace.endSection();
+ }
}
@Override
@@ -216,4 +236,37 @@ public class ImageFloatingTextView extends TextView {
requestLayout();
}
}
+
+ private void trackParameters() {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+ Trace.setCounter("ImageFloatingView#staticLayoutCreationCount",
+ mStaticLayoutCreationCountInOnMeasure);
+ Trace.setCounter("ImageFloatingView#isPrecomputedText",
+ isTextAPrecomputedText());
+ }
+ /**
+ * @return 1 if {@link TextView#getText()} is PrecomputedText, else 0
+ */
+ private int isTextAPrecomputedText() {
+ final CharSequence text = getText();
+ if (text == null) {
+ return 0;
+ }
+
+ if (text instanceof PrecomputedText) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ private void trackMaxLines() {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+
+ Trace.setCounter("ImageFloatingView#layoutMaxLines", mLayoutMaxLines);
+ }
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 757978b71a01..b5b3a48dacb7 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -333,11 +333,17 @@ public class LockPatternUtils {
@UnsupportedAppUsage
public LockPatternUtils(Context context) {
+ this(context, null);
+ }
+
+ @VisibleForTesting
+ public LockPatternUtils(Context context, ILockSettings lockSettings) {
mContext = context;
mContentResolver = context.getContentResolver();
Looper looper = Looper.myLooper();
mHandler = looper != null ? new Handler(looper) : null;
+ mLockSettingsService = lockSettings;
}
@UnsupportedAppUsage
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index c06f5f75514f..e07acac52f2c 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -21,6 +21,8 @@ import android.annotation.Px;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
+import android.os.Build;
+import android.os.Trace;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -45,6 +47,8 @@ public class MessagingLinearLayout extends ViewGroup {
private int mMaxDisplayedLines = Integer.MAX_VALUE;
+ private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
+
public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -67,6 +71,10 @@ public class MessagingLinearLayout extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (TRACE_ONMEASURE) {
+ Trace.beginSection("MessagingLinearLayout#onMeasure");
+ trackMeasureSpecs(widthMeasureSpec, heightMeasureSpec);
+ }
// This is essentially a bottom-up linear layout that only adds children that fit entirely
// up to a maximum height.
int targetHeight = MeasureSpec.getSize(heightMeasureSpec);
@@ -177,6 +185,9 @@ public class MessagingLinearLayout extends ViewGroup {
resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
widthMeasureSpec),
Math.max(getSuggestedMinimumHeight(), totalHeight));
+ if (TRACE_ONMEASURE) {
+ Trace.endSection();
+ }
}
@Override
@@ -240,6 +251,25 @@ public class MessagingLinearLayout extends ViewGroup {
}
}
+ private void trackMeasureSpecs(int widthMeasureSpec, int heightMeasureSpec) {
+ if (!TRACE_ONMEASURE) {
+ return;
+ }
+
+ final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecSize",
+ availableWidth);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_widthMeasureSpecMode",
+ widthMode);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecSize",
+ availableHeight);
+ Trace.setCounter("MessagingLinearLayout#onMeasure_heightMeasureSpecMode",
+ heightMode);
+ }
+
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
diff --git a/core/java/com/android/internal/widget/PeopleHelper.java b/core/java/com/android/internal/widget/PeopleHelper.java
index 85cedc362b99..3f5b4a0d61fe 100644
--- a/core/java/com/android/internal/widget/PeopleHelper.java
+++ b/core/java/com/android/internal/widget/PeopleHelper.java
@@ -22,6 +22,8 @@ import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.Person;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -222,6 +224,72 @@ public class PeopleHelper {
}
/**
+ * A class that represents a map from unique sender names in the groups to the string 1- or
+ * 2-character prefix strings for the names. This class uses the String value of the
+ * CharSequence Names as the key.
+ */
+ public class NameToPrefixMap {
+ Map<String, String> mMap;
+ NameToPrefixMap(Map<String, String> map) {
+ this.mMap = map;
+ }
+
+ /**
+ * @param name the name
+ * @return the prefix of the given name
+ */
+ public String getPrefix(CharSequence name) {
+ return mMap.get(name.toString());
+ }
+ }
+
+ /**
+ * Same functionality as mapUniqueNamesToPrefix, but takes list-represented message groups as
+ * the input. This method is better when inflating MessagingGroup from the UI thread is not
+ * an option.
+ * @param groups message groups represented by lists. A message group is some consecutive
+ * messages (>=3) from the same sender in a conversation.
+ */
+ public NameToPrefixMap mapUniqueNamesToPrefixWithGroupList(
+ List<List<Notification.MessagingStyle.Message>> groups) {
+ // Map of unique names to their prefix
+ ArrayMap<String, String> uniqueNames = new ArrayMap<>();
+ // Map of single-character string prefix to the only name which uses it, or null if multiple
+ ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
+ for (int i = 0; i < groups.size(); i++) {
+ List<Notification.MessagingStyle.Message> group = groups.get(i);
+ if (group.isEmpty()) continue;
+ Person sender = group.get(0).getSenderPerson();
+ if (sender == null) continue;
+ CharSequence senderName = sender.getName();
+ if (sender.getIcon() != null || TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ String senderNameString = senderName.toString();
+ if (!uniqueNames.containsKey(senderNameString)) {
+ String charPrefix = findNamePrefix(senderName, null);
+ if (charPrefix == null) {
+ continue;
+ }
+ if (uniqueCharacters.containsKey(charPrefix)) {
+ // this character was already used, lets make it more unique. We first need to
+ // resolve the existing character if it exists
+ CharSequence existingName = uniqueCharacters.get(charPrefix);
+ if (existingName != null) {
+ uniqueNames.put(existingName.toString(), findNameSplit(existingName));
+ uniqueCharacters.put(charPrefix, null);
+ }
+ uniqueNames.put(senderNameString, findNameSplit(senderName));
+ } else {
+ uniqueNames.put(senderNameString, charPrefix);
+ uniqueCharacters.put(charPrefix, senderName);
+ }
+ }
+ }
+ return new NameToPrefixMap(uniqueNames);
+ }
+
+ /**
* Update whether the groups can hide the sender if they are first
* (happens only for 1:1 conversations where the given title matches the sender's name)
*/
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 7af69f2dff08..d2e58bb62c46 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -28,6 +28,7 @@
#include <meminfo/sysmeminfo.h>
#include <processgroup/processgroup.h>
#include <processgroup/sched_policy.h>
+#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <algorithm>
@@ -232,6 +233,31 @@ void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int
}
}
+// Look up the user ID of a process in /proc/${pid}/status. The Uid: line is present in
+// /proc/${pid}/status since at least kernel v2.5.
+static int uid_from_pid(int pid)
+{
+ int uid = -1;
+ std::array<char, 64> path;
+ int res = snprintf(path.data(), path.size(), "/proc/%d/status", pid);
+ if (res < 0 || res >= static_cast<int>(path.size())) {
+ DCHECK(false);
+ return uid;
+ }
+ FILE* f = fopen(path.data(), "r");
+ if (!f) {
+ return uid;
+ }
+ char line[256];
+ while (fgets(line, sizeof(line), f)) {
+ if (sscanf(line, "Uid: %d", &uid) == 1) {
+ break;
+ }
+ }
+ fclose(f);
+ return uid;
+}
+
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
@@ -275,7 +301,12 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin
}
}
- if (!SetProcessProfilesCached(0, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
+ const int uid = uid_from_pid(pid);
+ if (uid < 0) {
+ signalExceptionForGroupError(env, ESRCH, pid);
+ return;
+ }
+ if (!SetProcessProfilesCached(uid, pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}))
signalExceptionForGroupError(env, errno ? errno : EPERM, pid);
}
@@ -1134,12 +1165,11 @@ static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid)
static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid)
{
- // total, file, anon, swap
- jlong rss[4] = {0, 0, 0, 0};
+ // total, file, anon, swap, shmem
+ jlong rss[5] = {0, 0, 0, 0, 0};
std::string status_path =
android::base::StringPrintf("/proc/%d/status", pid);
UniqueFile file = MakeUniqueFile(status_path.c_str(), "re");
-
char line[256];
while (file != nullptr && fgets(line, sizeof(line), file.get())) {
jlong v;
@@ -1151,17 +1181,18 @@ static jlongArray android_os_Process_getRss(JNIEnv* env, jobject clazz, jint pid
rss[2] = v;
} else if ( sscanf(line, "VmSwap: %" SCNd64 " kB", &v) == 1) {
rss[3] = v;
+ } else if ( sscanf(line, "RssShmem: %" SCNd64 " kB", &v) == 1) {
+ rss[4] = v;
}
}
- jlongArray rssArray = env->NewLongArray(4);
+ jlongArray rssArray = env->NewLongArray(5);
if (rssArray == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
return NULL;
}
- env->SetLongArrayRegion(rssArray, 0, 4, rss);
-
+ env->SetLongArrayRegion(rssArray, 0, 5, rss);
return rssArray;
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 25b2aaf3d737..98f409a334dc 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -343,7 +343,9 @@ public:
const std::vector<SurfaceControlStats>& /*stats*/) {
JNIEnv* env = getenv();
// Adding a strong reference for java SyncFence
- presentFence->incStrong(0);
+ if (presentFence) {
+ presentFence->incStrong(0);
+ }
jobject stats =
env->NewObject(gTransactionStatsClassInfo.clazz, gTransactionStatsClassInfo.ctor,
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 6e903b3ab56d..1031542eb2e6 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -211,7 +211,7 @@ static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptu
}
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
- jlong screenCaptureListenerObject) {
+ jlong screenCaptureListenerObject, jboolean sync) {
LayerCaptureArgs captureArgs;
getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
@@ -227,7 +227,7 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA
sp<gui::IScreenCaptureListener> captureListener =
reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
- return ScreenshotClient::captureLayers(captureArgs, captureListener);
+ return ScreenshotClient::captureLayers(captureArgs, captureListener, sync);
}
static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
@@ -281,7 +281,7 @@ static const JNINativeMethod sScreenCaptureMethods[] = {
// clang-format off
{"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
(void*)nativeCaptureDisplay },
- {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
+ {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;JZ)I",
(void*)nativeCaptureLayers },
{"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J",
(void*)nativeCreateScreenCaptureListener },
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index b63021d52f1c..c92435f61ab1 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -464,6 +464,7 @@ message WindowStateProto {
repeated .android.graphics.RectProto keep_clear_areas = 45;
repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
+ optional int32 requested_visible_types = 48;
}
message IdentifierProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5dae1d424e48..d972556df015 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6654,7 +6654,14 @@
<!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature" />
+
+ <!-- Allows an application to set the BiometricDialog (SystemUI) logo .
+ <p>Not for use by third-party applications.
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
+ -->
+ <permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO"
+ android:protectionLevel="signature" />
<!-- Allows an application to control keyguard. Only allowed for system processes.
@hide -->
@@ -7065,6 +7072,7 @@
android:protectionLevel="signature" />
<!-- @SystemApi Allows an application to access the smartspace service as a client.
+ @FlaggedApi(android.app.smartspace.flags.Flags.FLAG_ACCESS_SMARTSPACE)
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.ACCESS_SMARTSPACE"
android:protectionLevel="signature|privileged|development" />
@@ -7800,6 +7808,16 @@
<permission android:name="android.permission.RUN_USER_INITIATED_JOBS"
android:protectionLevel="normal"/>
+ <!-- @FlaggedApi("android.app.job.backup_jobs_exemption")
+ Gives applications whose <b>primary use case</b> is to backup or sync content increased
+ job execution allowance in order to complete the related work. The jobs must have a valid
+ content URI trigger and network constraint set.
+ <p>This is a special access permission that can be revoked by the system or the user.
+ <p>Protection level: signature|privileged|appop
+ -->
+ <permission android:name="android.permission.RUN_BACKUP_JOBS"
+ android:protectionLevel="signature|privileged|appop"/>
+
<!-- Allows an app access to the installer provided app metadata.
@SystemApi
@hide
@@ -7961,11 +7979,21 @@
<!-- @SystemApi Allows an application to read the system grammatical gender.
@FlaggedApi("android.app.system_terms_of_address_enabled")
- <p>Protection level: signature|privileged|appop
+ <p>Protection level: signature|privileged
@hide
-->
<permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
- android:protectionLevel="signature|privileged|appop"/>
+ android:protectionLevel="signature|privileged"/>
+
+ <!-- @SystemApi
+ @FlaggedApi("android.content.pm.emergency_install_permission")
+ Allows each app store in the system image to designate another app in the system image to
+ update the app store
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES"
+ android:protectionLevel="signature|privileged"/>
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
@@ -8373,6 +8401,16 @@
</intent-filter>
</receiver>
+ <!-- Broadcast Receiver listens to sufficient verifier broadcast from Package Manager
+ when installing new SDK. Verification of SDK code during installation time is run
+ to determine compatibility with privacy sandbox restrictions. -->
+ <receiver android:name="com.android.server.sdksandbox.SdkSandboxVerifierReceiver"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+ </intent-filter>
+ </receiver>
+
<service android:name="android.hardware.location.GeofenceHardwareService"
android:permission="android.permission.LOCATION_HARDWARE"
android:exported="false" />
@@ -8414,6 +8452,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.selinux.SelinuxAuditLogsService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.compos.IsolatedCompilationJobService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/core/res/res/color-night/notification_expand_button_state_tint.xml b/core/res/res/color-night/notification_expand_button_state_tint.xml
deleted file mode 100644
index a794d53c7e71..000000000000
--- a/core/res/res/color-night/notification_expand_button_state_tint.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<!--
- ~ 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.
- -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.06"/>
- <item android:state_hovered="true" android:color="@android:color/system_on_surface_dark" android:alpha="0.03"/>
- <item android:color="@android:color/system_on_surface_dark" android:alpha="0.00"/>
-</selector> \ No newline at end of file
diff --git a/core/res/res/color/notification_expand_button_state_tint.xml b/core/res/res/color/notification_expand_button_state_tint.xml
index 67b2c2568bb1..5a8594f0e461 100644
--- a/core/res/res/color/notification_expand_button_state_tint.xml
+++ b/core/res/res/color/notification_expand_button_state_tint.xml
@@ -14,8 +14,11 @@
~ limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:color="@android:color/system_on_surface_light" android:alpha="0.12"/>
- <item android:state_hovered="true" android:color="@android:color/system_on_surface_light" android:alpha="0.08"/>
- <item android:color="@android:color/system_on_surface_light" android:alpha="0.00"/>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:state_pressed="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+ android:alpha="0.15"/>
+ <item android:state_hovered="true" android:color="?androidprv:attr/materialColorOnPrimaryFixed"
+ android:alpha="0.11"/>
+ <item android:color="@color/transparent" />
</selector> \ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0d1a98785695..23c78fdf108e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6611,7 +6611,7 @@
</string-array>
<!-- Whether or not the monitoring on the apps' background battery drain is enabled -->
- <bool name="config_bg_current_drain_monitor_enabled">true</bool>
+ <bool name="config_bg_current_drain_monitor_enabled">false</bool>
<!-- The threshold of the background current drain (in percentage) to the restricted
standby bucket.
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 3a2e50aa06e8..9bb249999d99 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -34,7 +34,7 @@
http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
<!-- Arab Emirates -->
- <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214" />
+ <shortcode country="ae" pattern="\\d{1,5}" free="1017|1355|3214|6253" />
<!-- Albania: 5 digits, known short codes listed -->
<shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
@@ -86,7 +86,7 @@
<shortcode country="cn" premium="1066.*" free="1065.*" />
<!-- Colombia: 1-6 digits (not confirmed) -->
- <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517" />
+ <shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
<!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -104,6 +104,12 @@
<!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
<shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}|4665" />
+ <!-- Dominican Republic: 1-6 digits (standard system default, not country specific) -->
+ <shortcode country="do" pattern="\\d{1,6}" free="912892" />
+
+ <!-- Ecuador: 1-6 digits (standard system default, not country specific) -->
+ <shortcode country="ec" pattern="\\d{1,6}" free="466453" />
+
<!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
<shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}|95034" />
@@ -154,8 +160,8 @@
http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
<shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
- <!-- Israel: 4 digits, known premium codes listed -->
- <shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
+ <!-- Israel: 1-5 digits, known premium codes listed -->
+ <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" />
<!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU:
https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
@@ -193,11 +199,14 @@
<shortcode country="mk" pattern="\\d{1,6}" free="129005|122" />
<!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
- <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453" />
+ <shortcode country="mx" pattern="\\d{4,6}" premium="53035|7766" free="26259|46645|50025|50052|5050|76551|88778|9963|91101|45453|550346" />
<!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
<shortcode country="my" pattern="\\d{5}" premium="32298|33776" free="22099|28288|66668" />
+ <!-- Namibia: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="na" pattern="\\d{1,5}" free="40005" />
+
<!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
<shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}|2223|6225|2223|1662" />
diff --git a/core/tests/InputMethodCoreTests/Android.bp b/core/tests/InputMethodCoreTests/Android.bp
new file mode 100644
index 000000000000..ac6462589e16
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/Android.bp
@@ -0,0 +1,66 @@
+package {
+ // 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"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "InputMethodCoreTests",
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/**/I*.aidl",
+ ],
+
+ dxflags: ["--core-library"],
+
+ static_libs: [
+ "collector-device-lib-platform",
+ "android-common",
+ "frameworks-core-util-lib",
+ "androidx.core_core",
+ "androidx.core_core-ktx",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "flag-junit",
+ "junit-params",
+ "kotlin-test",
+ "mockito-target-minus-junit4",
+ "platform-test-annotations",
+ "platform-compat-test-rules",
+ "truth",
+ "print-test-util-lib",
+ "testng",
+ "device-time-shell-utils",
+ "testables",
+ "flag-junit",
+ ],
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "framework",
+ "ext",
+ "framework-res",
+ ],
+
+ sdk_version: "core_platform",
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+
+ certificate: "platform",
+
+ resource_dirs: ["res"],
+
+ data: [
+ ":com.android.cts.helpers.aosp",
+ ],
+}
diff --git a/core/tests/InputMethodCoreTests/AndroidManifest.xml b/core/tests/InputMethodCoreTests/AndroidManifest.xml
new file mode 100644
index 000000000000..8d00d0f755bf
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ android:installLocation="internalOnly"
+ package="com.android.frameworks.inputmethodcoretests"
+ android:sharedUserId="com.android.uid.test">
+
+ <application
+ android:supportsRtl="true"
+ android:enableOnBackInvokedCallback="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.frameworks.inputmethodcoretests"
+ android:label="InputMethod Core Tests" />
+</manifest>
diff --git a/core/tests/InputMethodCoreTests/AndroidTest.xml b/core/tests/InputMethodCoreTests/AndroidTest.xml
new file mode 100644
index 000000000000..fa585d8d1075
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs InputMethod Core Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="InputMethodCoreTests.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <!-- TODO(b/254155965): Design a mechanism to finally remove this command. -->
+ <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DeviceInteractionHelperInstaller" />
+
+ <option name="test-tag" value="InputMethodCoreTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.frameworks.inputmethodcoretests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/core/tests/InputMethodCoreTests/OWNERS b/core/tests/InputMethodCoreTests/OWNERS
new file mode 100644
index 000000000000..5deb2ce8f24b
--- /dev/null
+++ b/core/tests/InputMethodCoreTests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/tests/coretests/res/xml/ime_meta.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
index a975718c50e9..a975718c50e9 100644
--- a/core/tests/coretests/res/xml/ime_meta.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
index e67bf6331bfe..e67bf6331bfe 100644
--- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
index 34402089b47d..34402089b47d 100644
--- a/core/tests/coretests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_inline_suggestions_with_touch_exploration.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_sw_next.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
index 2e2ee33e9933..2e2ee33e9933 100644
--- a/core/tests/coretests/res/xml/ime_meta_sw_next.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_sw_next.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
index 1905365808bc..1905365808bc 100644
--- a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_virtual_device_only.xml
diff --git a/core/tests/coretests/res/xml/ime_meta_vr_only.xml b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
index 653a8ffcb944..653a8ffcb944 100644
--- a/core/tests/coretests/res/xml/ime_meta_vr_only.xml
+++ b/core/tests/InputMethodCoreTests/res/xml/ime_meta_vr_only.xml
diff --git a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
index f04f603564f6..f04f603564f6 100644
--- a/core/tests/coretests/src/android/view/inputmethod/BaseInputConnectionTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/BaseInputConnectionTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
index 9d7d71d8d539..9d7d71d8d539 100644
--- a/core/tests/coretests/src/android/view/inputmethod/CursorAnchorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/CursorAnchorInfoTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
index d7b911dda672..d7b911dda672 100644
--- a/core/tests/coretests/src/android/view/inputmethod/DeleteRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/DeleteRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
index 4839dd27b283..4839dd27b283 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/EditorInfoTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
index 909af7b4c5fb..a3f537ef5f1c 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -32,7 +32,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.frameworks.coretests.R;
+import com.android.frameworks.inputmethodcoretests.R;
import org.junit.Rule;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
index d70572444128..d70572444128 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodManagerTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodManagerTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
index e7b1110f898a..e7b1110f898a 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
index 5095cad1b607..5095cad1b607 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InputMethodSubtypeTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
index 47a724d36038..47a724d36038 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InsertGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
index a94f8772fcd0..a94f8772fcd0 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InsertModeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/InsertModeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
index 90f7d06857c7..90f7d06857c7 100644
--- a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
index b2eb07c0a9e7..b2eb07c0a9e7 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SelectGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
index df63a4aaaefe..df63a4aaaefe 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SelectRangeGestureTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SelectRangeGestureTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
index f264cc630dc5..f264cc630dc5 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SparseRectFArrayTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SparseRectFArrayTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
index 047f33074460..047f33074460 100644
--- a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/SurroundingTextTest.java
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
index 0750cf1a64ab..0750cf1a64ab 100644
--- a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
+++ b/core/tests/InputMethodCoreTests/src/android/view/inputmethod/TextAppearanceInfoTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
index 07471f08e0f5..07471f08e0f5 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
index a6262944e8b0..a6262944e8b0 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputConnectionWrapperTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
index 32bfdcb7b217..32bfdcb7b217 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodDebugTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
index f111bf6fcd64..f111bf6fcd64 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
index ba6390808151..ba6390808151 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
+++ b/core/tests/InputMethodCoreTests/src/com/android/internal/inputmethod/SubtypeLocaleUtilsTest.java
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d1a90aea835c..f47679991418 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -209,11 +209,15 @@ android_ravenwood_test {
"testng",
],
srcs: [
+ "src/android/content/pm/PackageManagerTest.java",
+ "src/android/content/pm/UserInfoTest.java",
"src/android/database/CursorWindowTest.java",
"src/android/os/**/*.java",
+ "src/android/telephony/PinResultTest.java",
"src/android/util/**/*.java",
+ "src/android/view/DisplayInfoTest.java",
+ "src/com/android/internal/logging/**/*.java",
"src/com/android/internal/os/**/*.java",
- "src/com/android/internal/os/LongArrayMultiStateCounterTest.java",
"src/com/android/internal/util/**/*.java",
"src/com/android/internal/power/EnergyConsumerStatsTest.java",
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTest.java b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
new file mode 100644
index 000000000000..20421d105db8
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PackageManagerTest {
+ @Test
+ public void testPackageInfoFlags() throws Exception {
+ assertThat(PackageManager.PackageInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+
+ @Test
+ public void testApplicationInfoFlags() throws Exception {
+ assertThat(PackageManager.ApplicationInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+
+ @Test
+ public void testComponentInfoFlags() throws Exception {
+ assertThat(PackageManager.ComponentInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+
+ @Test
+ public void testResolveInfoFlags() throws Exception {
+ assertThat(PackageManager.ResolveInfoFlags.of(42L).getValue()).isEqualTo(42L);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/pm/UserInfoTest.java b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
new file mode 100644
index 000000000000..af36dbb36379
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/UserInfoTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.UserHandle;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UserInfoTest {
+ @Test
+ public void testSimple() throws Exception {
+ final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+ assertThat(ui.getUserHandle()).isEqualTo(UserHandle.of(10));
+ assertThat(ui.name).isEqualTo("Test");
+
+ // Derived based on userType field
+ assertThat(ui.isManagedProfile()).isEqualTo(false);
+ assertThat(ui.isGuest()).isEqualTo(true);
+ assertThat(ui.isRestricted()).isEqualTo(false);
+ assertThat(ui.isDemo()).isEqualTo(false);
+ assertThat(ui.isCloneProfile()).isEqualTo(false);
+ assertThat(ui.isCommunalProfile()).isEqualTo(false);
+ assertThat(ui.isPrivateProfile()).isEqualTo(false);
+
+ // Derived based on flags field
+ assertThat(ui.isPrimary()).isEqualTo(false);
+ assertThat(ui.isAdmin()).isEqualTo(false);
+ assertThat(ui.isProfile()).isEqualTo(false);
+ assertThat(ui.isEnabled()).isEqualTo(true);
+ assertThat(ui.isQuietModeEnabled()).isEqualTo(false);
+ assertThat(ui.isEphemeral()).isEqualTo(false);
+ assertThat(ui.isForTesting()).isEqualTo(false);
+ assertThat(ui.isInitialized()).isEqualTo(false);
+ assertThat(ui.isFull()).isEqualTo(false);
+ assertThat(ui.isMain()).isEqualTo(false);
+
+ // Derived dynamically
+ assertThat(ui.canHaveProfile()).isEqualTo(false);
+ }
+
+ @Test
+ public void testDebug() throws Exception {
+ final UserInfo ui = new UserInfo(10, "Test", UserInfo.FLAG_GUEST);
+
+ assertThat(ui.toString()).isNotEmpty();
+ assertThat(ui.toFullString()).isNotEmpty();
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index e32a57b1aefe..a2a5433eca24 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -145,7 +145,6 @@ class FontScaleConverterFactoryTest {
fun unnecessaryFontScalesReturnsNull() {
assertThat(FontScaleConverterFactory.forScale(0F)).isNull()
assertThat(FontScaleConverterFactory.forScale(1F)).isNull()
- assertThat(FontScaleConverterFactory.forScale(1.1F)).isNull()
assertThat(FontScaleConverterFactory.forScale(0.85F)).isNull()
}
@@ -176,7 +175,7 @@ class FontScaleConverterFactoryTest {
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(-1f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(0.85f)).isFalse()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.02f)).isFalse()
- assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isFalse()
+ assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.10f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.15f)).isTrue()
assertThat(FontScaleConverterFactory.isNonLinearFontScalingActive(1.1499999f))
.isTrue()
diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java
index 2d3e12331e23..2a718ff2f4aa 100644
--- a/core/tests/coretests/src/android/os/BuildTest.java
+++ b/core/tests/coretests/src/android/os/BuildTest.java
@@ -20,7 +20,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -71,7 +70,6 @@ public class BuildTest {
*/
@Test
@SmallTest
- @IgnoreUnderRavenwood(blockedBy = Build.class)
public void testBuildFields() throws Exception {
assertNotEmpty("ID", Build.ID);
assertNotEmpty("DISPLAY", Build.DISPLAY);
diff --git a/core/tests/coretests/src/android/os/TraceTest.java b/core/tests/coretests/src/android/os/TraceTest.java
index 593833ec96a7..b2c005f8a4f4 100644
--- a/core/tests/coretests/src/android/os/TraceTest.java
+++ b/core/tests/coretests/src/android/os/TraceTest.java
@@ -34,7 +34,6 @@ import org.junit.runner.RunWith;
* while tracing on the emulator and then run traceview to view the trace.
*/
@RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Trace.class)
public class TraceTest {
private static final String TAG = "TraceTest";
@@ -46,7 +45,51 @@ public class TraceTest {
private int gMethodCalls = 0;
@Test
+ public void testEnableDisable() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.setTracingEnabled(true, 0);
+ Trace.setTracingEnabled(false, 0);
+
+ Trace.setAppTracingAllowed(true);
+ Trace.setAppTracingAllowed(false);
+ }
+
+ @Test
+ public void testBeginEnd() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+ Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+ Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG, 42);
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+
+ Trace.beginSection(TAG);
+ Trace.endSection();
+
+ Trace.beginAsyncSection(TAG, 42);
+ Trace.endAsyncSection(TAG, 42);
+ }
+
+ @Test
+ public void testCounter() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, 42);
+ Trace.setCounter(TAG, 42);
+ }
+
+ @Test
+ public void testInstant() {
+ // Currently only verifying that we can invoke without crashing
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG);
+ Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, TAG, TAG);
+ }
+
+ @Test
public void testNullStrings() {
+ // Currently only verifying that we can invoke without crashing
Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER, null, 42);
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, null);
@@ -62,6 +105,7 @@ public class TraceTest {
@Test
@SmallTest
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void testNativeTracingFromJava()
{
long start = System.currentTimeMillis();
@@ -82,6 +126,7 @@ public class TraceTest {
// This should not run in the automated suite.
@Suppress
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void disableTestNativeTracingFromC()
{
long start = System.currentTimeMillis();
@@ -97,6 +142,7 @@ public class TraceTest {
@Test
@LargeTest
@Suppress // Failing.
+ @IgnoreUnderRavenwood(blockedBy = Debug.class)
public void testMethodTracing()
{
long start = System.currentTimeMillis();
diff --git a/core/tests/coretests/src/android/telephony/PinResultTest.java b/core/tests/coretests/src/android/telephony/PinResultTest.java
new file mode 100644
index 000000000000..c260807e5cbc
--- /dev/null
+++ b/core/tests/coretests/src/android/telephony/PinResultTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PinResultTest {
+ @Test
+ public void testSimple() throws Exception {
+ final PinResult res = new PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 5);
+ assertThat(res.getResult()).isEqualTo(PinResult.PIN_RESULT_TYPE_SUCCESS);
+ assertThat(res.getAttemptsRemaining()).isEqualTo(5);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/SingletonTest.java b/core/tests/coretests/src/android/util/SingletonTest.java
new file mode 100644
index 000000000000..8c5a9639c23a
--- /dev/null
+++ b/core/tests/coretests/src/android/util/SingletonTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SingletonTest {
+ @Test
+ public void testSimple() throws Exception {
+ final Singleton<Object> singleton = new Singleton<>() {
+ @Override
+ protected Object create() {
+ return new Object();
+ }
+ };
+
+ final Object first = singleton.get();
+ final Object second = singleton.get();
+ assertTrue(first == second);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java
index 803d38c4208a..4c5b7e508e34 100644
--- a/core/tests/coretests/src/android/view/DisplayInfoTest.java
+++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java
@@ -21,9 +21,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import android.platform.test.ravenwood.RavenwoodRule;
+
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +35,9 @@ import org.junit.runner.RunWith;
public class DisplayInfoTest {
private static final float FLOAT_EQUAL_DELTA = 0.0001f;
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
@Test
public void testDefaultDisplayInfosAreEqual() {
DisplayInfo displayInfo1 = new DisplayInfo();
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 906d84ec96b6..672875a19f9f 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -20,8 +20,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ISIDE_BOTTOM;
-import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsSource.SIDE_BOTTOM;
+import static android.view.InsetsSource.SIDE_TOP;
import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
import static android.view.RoundedCorner.POSITION_TOP_LEFT;
@@ -106,8 +106,8 @@ public class InsetsStateTest {
typeSideMap);
assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
- assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
- assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME));
+ assertEquals(SIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
+ assertEquals(SIDE_BOTTOM, typeSideMap.get(ID_IME));
assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
index f0f3a9683353..00751288ad62 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionV2Test.java
@@ -433,6 +433,72 @@ public class MainContentCaptureSessionV2Test {
assertThat(session.mEvents).isEmpty();
}
+ @Test
+ public void notifyViewAppearedBelowMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE - 1; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(0))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isNull();
+ assertThat(session.mEventProcessQueue).hasSize(BUFFER_SIZE - 1);
+ }
+
+ @Test
+ public void notifyViewAppearedExactAsMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(1))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isEmpty();
+ assertThat(session.mEventProcessQueue).isEmpty();
+ }
+
+ @Test
+ public void notifyViewAppearedAboveMaximumBufferSize() throws RemoteException {
+ ContentCaptureOptions options =
+ createOptions(
+ /* enableContentCaptureReceiver= */ true,
+ /* enableContentProtectionReceiver= */ true);
+ MainContentCaptureSessionV2 session = createSession(options);
+ session.mDirectServiceInterface = mMockContentCaptureDirectManager;
+
+ session.onSessionStarted(0x2, null);
+ for (int i = 0; i < BUFFER_SIZE * 2 + 1; i++) {
+ View view = prepareView(session);
+ session.notifyViewAppeared(session.newViewStructure(view));
+ }
+ mTestableLooper.processAllMessages();
+
+ verify(mMockContentCaptureDirectManager, times(2))
+ .sendEvents(any(), anyInt(), any());
+ assertThat(session.mEvents).isEmpty();
+ assertThat(session.mEventProcessQueue).hasSize(1);
+ }
+
/** Simulates the regular content capture events sequence. */
private void notifyContentCaptureEvents(final MainContentCaptureSessionV2 session) {
final ArrayList<Object> events = new ArrayList<>(
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index a709d7be898b..52ff0d4037e8 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -20,6 +20,7 @@ import static android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT;
import static android.window.OnBackInvokedDispatcher.PRIORITY_OVERLAY;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
@@ -358,7 +359,7 @@ public class WindowOnBackInvokedDispatcherTest {
}
@Test
- public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
+ public void onDetachFromWindow_cancelsBackAnimation() throws RemoteException {
mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
@@ -368,13 +369,12 @@ public class WindowOnBackInvokedDispatcherTest {
waitForIdle();
verify(mCallback1).onBackStarted(any(BackEvent.class));
- // This should trigger mCallback1.onBackCancelled()
+ // This should trigger mCallback1.onBackCancelled() and unset the callback in WM
mDispatcher.detachFromWindow();
- // This should be ignored by mCallback1
- callbackInfo.getCallback().onBackInvoked();
+ OnBackInvokedCallbackInfo callbackInfo1 = assertSetCallbackInfo();
+ assertNull(callbackInfo1);
waitForIdle();
- verify(mCallback1, never()).onBackInvoked();
verify(mCallback1).onBackCancelled();
}
}
diff --git a/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
new file mode 100644
index 000000000000..7054cc0f24b4
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/MetricsLoggerTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.metrics.LogMaker;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.testing.FakeMetricsLogger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MetricsLoggerTest {
+ private FakeMetricsLogger mLogger;
+
+ private static final int TEST_ACTION = 42;
+
+ @Before
+ public void setUp() throws Exception {
+ mLogger = new FakeMetricsLogger();
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ assertThat(mLogger.getLogs().size()).isEqualTo(0);
+ }
+
+ @Test
+ public void testAction() throws Exception {
+ mLogger.action(TEST_ACTION);
+ assertThat(mLogger.getLogs().size()).isEqualTo(1);
+ final LogMaker event = mLogger.getLogs().peek();
+ assertThat(event.getType()).isEqualTo(MetricsProto.MetricsEvent.TYPE_ACTION);
+ assertThat(event.getCategory()).isEqualTo(TEST_ACTION);
+ }
+
+ @Test
+ public void testVisible() throws Exception {
+ // Limited testing to confirm we don't crash
+ mLogger.visible(TEST_ACTION);
+ mLogger.hidden(TEST_ACTION);
+ mLogger.visibility(TEST_ACTION, true);
+ mLogger.visibility(TEST_ACTION, false);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
new file mode 100644
index 000000000000..7840f7177278
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/logging/UiEventLoggerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.logging.testing.UiEventLoggerFake;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class UiEventLoggerTest {
+ private UiEventLoggerFake mLogger;
+
+ private static final int TEST_EVENT_ID = 42;
+ private static final int TEST_INSTANCE_ID = 21;
+
+ private enum MyUiEventEnum implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Example event")
+ TEST_EVENT(TEST_EVENT_ID);
+
+ private final int mId;
+
+ MyUiEventEnum(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+ private InstanceId TEST_INSTANCE = InstanceId.fakeInstanceId(TEST_INSTANCE_ID);
+
+ @Before
+ public void setUp() throws Exception {
+ mLogger = new UiEventLoggerFake();
+ }
+
+ @Test
+ public void testEmpty() throws Exception {
+ assertThat(mLogger.numLogs()).isEqualTo(0);
+ }
+
+ @Test
+ public void testSimple() throws Exception {
+ mLogger.log(MyUiEventEnum.TEST_EVENT);
+ assertThat(mLogger.numLogs()).isEqualTo(1);
+ assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+ }
+
+ @Test
+ public void testWithInstance() throws Exception {
+ mLogger.log(MyUiEventEnum.TEST_EVENT, TEST_INSTANCE);
+ assertThat(mLogger.numLogs()).isEqualTo(1);
+ assertThat(mLogger.eventId(0)).isEqualTo(TEST_EVENT_ID);
+ assertThat(mLogger.get(0).instanceId.getId()).isEqualTo(TEST_INSTANCE_ID);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index ec4c563e469e..06d888b59166 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,10 +18,14 @@ package com.android.internal.os;
import static com.google.common.truth.Truth.assertThat;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -33,6 +37,9 @@ import java.nio.file.Files;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MonotonicClockTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
private final MockClock mClock = new MockClock();
private File mFile;
@@ -70,6 +77,7 @@ public class MonotonicClockTest {
}
@Test
+ @IgnoreUnderRavenwood(reason = "b/321832617")
public void corruptedFile() throws IOException {
// Create an invalid binary XML file to cause IOException: "Unexpected magic number"
try (FileWriter w = new FileWriter(mFile)) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
index 1a668f7bdc8f..b90480a1e794 100644
--- a/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/LockPatternUtilsTest.java
@@ -16,20 +16,279 @@
package com.android.internal.widget;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.trust.TrustManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.UserInfo;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
public class LockPatternUtilsTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+ private ILockSettings mLockSettings;
+ private static final int USER_ID = 1;
+ private static final int DEMO_USER_ID = 5;
+
+ private LockPatternUtils mLockPatternUtils;
+
+ private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
+ throws Exception {
+ mLockSettings = mock(ILockSettings.class);
+ final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ final MockContentResolver cr = new MockContentResolver(context);
+ cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(context.getContentResolver()).thenReturn(cr);
+ Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
+
+ when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
+ isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+ : LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
+ DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
+ when(mLockSettings.hasSecureLockScreen()).thenReturn(true);
+ mLockPatternUtils = new LockPatternUtils(context, mLockSettings);
+
+ final UserInfo userInfo = mock(UserInfo.class);
+ when(userInfo.isDemo()).thenReturn(isDemoUser);
+ final UserManager um = mock(UserManager.class);
+ when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
+ when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
+ }
+
+ @Test
+ public void isUserInLockDown() throws Exception {
+ configureTest(true, false, 2);
+
+ // GIVEN strong auth not required
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
+
+ // THEN user isn't in lockdown
+ assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+
+ // GIVEN lockdown and lockout
+ when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ // THEN user is in lockdown
+ assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isDemoUser_true() throws Exception {
+ configureTest(false, true, 2);
+ assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
+ configureTest(true, true, 2);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
+ configureTest(false, false, 2);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
+ configureTest(false, true, 0);
+ assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
+ }
+
+ @Test
+ public void testAddWeakEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ int testUserId = 10;
+ IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+ mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+ verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+ }
+
+ @Test
+ public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+ mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+ verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+ }
+
+ @Test
+ public void testRemoveAutoEscrowToken() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+ verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenActive() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+ verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+ }
+
+ @Test
+ public void testIsAutoEscrowTokenValid() throws RemoteException {
+ ILockSettings ils = createTestLockSettings();
+ int testUserId = 10;
+ byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+ long testHandle = 100L;
+ mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+ verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+ }
+
+ @Test
+ public void testSetEnabledTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> enabledTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetEnabledTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
+ List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+ }
+
+ @Test
+ public void testSetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
+ doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
+ List<ComponentName> knownTrustAgents = Lists.newArrayList(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+
+ mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
+
+ assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
+ }
+
+ @Test
+ public void testGetKnownTrustAgents() throws RemoteException {
+ int testUserId = 10;
+ ILockSettings ils = createTestLockSettings();
+ when(ils.getString(anyString(), any(), anyInt())).thenReturn(
+ "com.android/.TrustAgent,com.test/.TestAgent");
+
+ List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
+
+ assertThat(trustAgents).containsExactly(
+ ComponentName.unflattenFromString("com.android/.TrustAgent"),
+ ComponentName.unflattenFromString("com.test/.TestAgent"));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
+
+ assertTrue(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
+
+ @Test
+ public void isBiometricAllowedForUser_afterLockout_returnsFalse()
+ throws RemoteException {
+ TestStrongAuthTracker tracker = createStrongAuthTracker();
+ tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
+
+ assertFalse(tracker.isBiometricAllowedForUser(
+ /* isStrongBiometric = */ true,
+ DEMO_USER_ID));
+ }
@Test
public void testUserFrp_isNotRegularUser() throws Exception {
@@ -56,4 +315,49 @@ public class LockPatternUtilsTest {
assertNotEquals(UserHandle.USER_CURRENT, LockPatternUtils.USER_REPAIR_MODE);
assertNotEquals(UserHandle.USER_CURRENT_OR_SELF, LockPatternUtils.USER_REPAIR_MODE);
}
+
+ private TestStrongAuthTracker createStrongAuthTracker() {
+ final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
+ return new TestStrongAuthTracker(context, Looper.getMainLooper());
+ }
+
+ private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
+
+ TestStrongAuthTracker(Context context, Looper looper) {
+ super(context, looper);
+ }
+
+ public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
+ handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
+ }
+ }
+
+ private ILockSettings createTestLockSettings() {
+ final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+
+ final TrustManager trustManager = mock(TrustManager.class);
+ when(context.getSystemService(Context.TRUST_SERVICE)).thenReturn(trustManager);
+
+ final ILockSettings ils = mock(ILockSettings.class);
+ mLockPatternUtils = new LockPatternUtils(context, ils);
+ return ils;
+ }
+
+ private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+ return new IWeakEscrowTokenActivatedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenActivated(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
+
+ private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+ return new IWeakEscrowTokenRemovedListener.Stub() {
+ @Override
+ public void onWeakEscrowTokenRemoved(long handle, int userId) {
+ // Do nothing.
+ }
+ };
+ }
}
diff --git a/core/tests/systemproperties/Android.bp b/core/tests/systemproperties/Android.bp
index 765ca3e5110a..21aa3c44044b 100644
--- a/core/tests/systemproperties/Android.bp
+++ b/core/tests/systemproperties/Android.bp
@@ -15,6 +15,9 @@ android_test {
static_libs: [
"android-common",
"frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
],
libs: [
"android.test.runner",
@@ -23,3 +26,22 @@ android_test {
platform_apis: true,
certificate: "platform",
}
+
+android_ravenwood_test {
+ name: "FrameworksCoreSystemPropertiesTestsRavenwood",
+ static_libs: [
+ "android-common",
+ "frameworks-core-util-lib",
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "ravenwood-junit",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ srcs: [
+ "src/**/*.java",
+ ],
+ auto_gen_config: true,
+}
diff --git a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
index 67783bff9299..ea65de088c07 100644
--- a/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
+++ b/core/tests/systemproperties/src/android/os/SystemPropertiesTest.java
@@ -16,19 +16,36 @@
package android.os;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.ravenwood.RavenwoodRule;
import android.test.suitebuilder.annotation.SmallTest;
-import junit.framework.TestCase;
+import org.junit.Rule;
+import org.junit.Test;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-public class SystemPropertiesTest extends TestCase {
+public class SystemPropertiesTest {
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+ .setSystemPropertyMutable(KEY, null)
+ .setSystemPropertyMutable(UNSET_KEY, null)
+ .setSystemPropertyMutable(PERSIST_KEY, null)
+ .build();
+
private static final String KEY = "sys.testkey";
private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
private static final String PERSIST_KEY = "persist.sys.testkey";
+ @Test
@SmallTest
public void testStressPersistPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -38,6 +55,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testStressMemoryPropertyConsistency() throws Exception {
for (int i = 0; i < 100; ++i) {
@@ -47,6 +65,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testProperties() throws Exception {
String value;
@@ -93,6 +112,7 @@ public class SystemPropertiesTest extends TestCase {
assertEquals(expected, value);
}
+ @Test
@SmallTest
public void testHandle() throws Exception {
String value;
@@ -114,6 +134,7 @@ public class SystemPropertiesTest extends TestCase {
assertEquals(12345, handle.getInt(12345));
}
+ @Test
@SmallTest
public void testIntegralProperties() throws Exception {
testInt("", 123, 123);
@@ -133,6 +154,7 @@ public class SystemPropertiesTest extends TestCase {
testLong("-3147483647", 124, -3147483647L);
}
+ @Test
@SmallTest
public void testUnset() throws Exception {
assertEquals("abc", SystemProperties.get(UNSET_KEY, "abc"));
@@ -142,6 +164,7 @@ public class SystemPropertiesTest extends TestCase {
assertEquals(-10, SystemProperties.getLong(UNSET_KEY, -10));
}
+ @Test
@SmallTest
@SuppressWarnings("null")
public void testNullKey() throws Exception {
@@ -176,6 +199,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testCallbacks() {
// Latches are not really necessary, but are easy to use.
@@ -220,6 +244,7 @@ public class SystemPropertiesTest extends TestCase {
}
}
+ @Test
@SmallTest
public void testDigestOf() {
final String empty = SystemProperties.digestOf();
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
deleted file mode 100644
index dcaf67660ffa..000000000000
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * Copyright (C) 2017 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.util;
-
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
-
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-import android.provider.Settings;
-import android.test.mock.MockContentResolver;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.internal.util.test.FakeSettingsProvider;
-import com.android.internal.widget.ILockSettings;
-import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
-import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
-import com.android.internal.widget.LockPatternUtils;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@IgnoreUnderRavenwood(blockedBy = LockPatternUtils.class)
-public class LockPatternUtilsTest {
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- private ILockSettings mLockSettings;
- private static final int USER_ID = 1;
- private static final int DEMO_USER_ID = 5;
-
- private LockPatternUtils mLockPatternUtils;
-
- private void configureTest(boolean isSecure, boolean isDemoUser, int deviceDemoMode)
- throws Exception {
- mLockSettings = Mockito.mock(ILockSettings.class);
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
-
- final MockContentResolver cr = new MockContentResolver(context);
- cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- when(context.getContentResolver()).thenReturn(cr);
- Settings.Global.putInt(cr, Settings.Global.DEVICE_DEMO_MODE, deviceDemoMode);
-
- when(mLockSettings.getCredentialType(DEMO_USER_ID)).thenReturn(
- isSecure ? LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
- : LockPatternUtils.CREDENTIAL_TYPE_NONE);
- when(mLockSettings.getLong("lockscreen.password_type", PASSWORD_QUALITY_UNSPECIFIED,
- DEMO_USER_ID)).thenReturn((long) PASSWORD_QUALITY_MANAGED);
- // TODO(b/63758238): stop spying the class under test
- mLockPatternUtils = spy(new LockPatternUtils(context));
- when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
- doReturn(true).when(mLockPatternUtils).hasSecureLockScreen();
-
- final UserInfo userInfo = Mockito.mock(UserInfo.class);
- when(userInfo.isDemo()).thenReturn(isDemoUser);
- final UserManager um = Mockito.mock(UserManager.class);
- when(um.getUserInfo(DEMO_USER_ID)).thenReturn(userInfo);
- when(context.getSystemService(Context.USER_SERVICE)).thenReturn(um);
- }
-
- @Test
- public void isUserInLockDown() throws Exception {
- configureTest(true, false, 2);
-
- // GIVEN strong auth not required
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(STRONG_AUTH_NOT_REQUIRED);
-
- // THEN user isn't in lockdown
- assertFalse(mLockPatternUtils.isUserInLockdown(USER_ID));
-
- // GIVEN lockdown
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
-
- // THEN user is in lockdown
- assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
-
- // GIVEN lockdown and lockout
- when(mLockSettings.getStrongAuthForUser(USER_ID)).thenReturn(
- STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN | STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
- // THEN user is in lockdown
- assertTrue(mLockPatternUtils.isUserInLockdown(USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isDemoUser_true() throws Exception {
- configureTest(false, true, 2);
- assertTrue(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isSecureAndDemoUser_false() throws Exception {
- configureTest(true, true, 2);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isNotDemoUser_false() throws Exception {
- configureTest(false, false, 2);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void isLockScreenDisabled_isNotInDemoMode_false() throws Exception {
- configureTest(false, true, 0);
- assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
- }
-
- @Test
- public void testAddWeakEscrowToken() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
- int testUserId = 10;
- IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
- mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
- verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
- }
-
- @Test
- public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
- mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
- verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
- }
-
- @Test
- public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
- mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
- verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
- }
-
- @Test
- public void testRemoveAutoEscrowToken() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- long testHandle = 100L;
- mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
- verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
- }
-
- @Test
- public void testIsAutoEscrowTokenActive() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- long testHandle = 100L;
- mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
- verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
- }
-
- @Test
- public void testIsAutoEscrowTokenValid() throws RemoteException {
- ILockSettings ils = createTestLockSettings();
- int testUserId = 10;
- byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
- long testHandle = 100L;
- mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
- verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
- }
-
- @Test
- public void testSetEnabledTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
- doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
- List<ComponentName> enabledTrustAgents = Lists.newArrayList(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
-
- mLockPatternUtils.setEnabledTrustAgents(enabledTrustAgents, testUserId);
-
- assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
- }
-
- @Test
- public void testGetEnabledTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- when(ils.getString(anyString(), any(), anyInt())).thenReturn(
- "com.android/.TrustAgent,com.test/.TestAgent");
-
- List<ComponentName> trustAgents = mLockPatternUtils.getEnabledTrustAgents(testUserId);
-
- assertThat(trustAgents).containsExactly(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
- }
-
- @Test
- public void testSetKnownTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- ArgumentCaptor<String> valueCaptor = ArgumentCaptor.forClass(String.class);
- doNothing().when(ils).setString(anyString(), valueCaptor.capture(), anyInt());
- List<ComponentName> knownTrustAgents = Lists.newArrayList(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
-
- mLockPatternUtils.setKnownTrustAgents(knownTrustAgents, testUserId);
-
- assertThat(valueCaptor.getValue()).isEqualTo("com.android/.TrustAgent,com.test/.TestAgent");
- }
-
- @Test
- public void testGetKnownTrustAgents() throws RemoteException {
- int testUserId = 10;
- ILockSettings ils = createTestLockSettings();
- when(ils.getString(anyString(), any(), anyInt())).thenReturn(
- "com.android/.TrustAgent,com.test/.TestAgent");
-
- List<ComponentName> trustAgents = mLockPatternUtils.getKnownTrustAgents(testUserId);
-
- assertThat(trustAgents).containsExactly(
- ComponentName.unflattenFromString("com.android/.TrustAgent"),
- ComponentName.unflattenFromString("com.test/.TestAgent"));
- }
-
- @Test
- public void isBiometricAllowedForUser_afterTrustagentExpired_returnsTrue()
- throws RemoteException {
- TestStrongAuthTracker tracker = createStrongAuthTracker();
- tracker.changeStrongAuth(SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED);
-
- assertTrue(tracker.isBiometricAllowedForUser(
- /* isStrongBiometric = */ true,
- DEMO_USER_ID));
- }
-
- @Test
- public void isBiometricAllowedForUser_afterLockout_returnsFalse()
- throws RemoteException {
- TestStrongAuthTracker tracker = createStrongAuthTracker();
- tracker.changeStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
-
- assertFalse(tracker.isBiometricAllowedForUser(
- /* isStrongBiometric = */ true,
- DEMO_USER_ID));
- }
-
-
- private TestStrongAuthTracker createStrongAuthTracker() {
- final Context context = new ContextWrapper(InstrumentationRegistry.getTargetContext());
- return new TestStrongAuthTracker(context, Looper.getMainLooper());
- }
-
- private static class TestStrongAuthTracker extends LockPatternUtils.StrongAuthTracker {
-
- TestStrongAuthTracker(Context context, Looper looper) {
- super(context, looper);
- }
-
- public void changeStrongAuth(@StrongAuthFlags int strongAuthFlags) {
- handleStrongAuthRequiredChanged(strongAuthFlags, DEMO_USER_ID);
- }
- }
-
- private ILockSettings createTestLockSettings() {
- final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
- mLockPatternUtils = spy(new LockPatternUtils(context));
- final ILockSettings ils = Mockito.mock(ILockSettings.class);
- when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
- return ils;
- }
-
- private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
- return new IWeakEscrowTokenActivatedListener.Stub() {
- @Override
- public void onWeakEscrowTokenActivated(long handle, int userId) {
- // Do nothing.
- }
- };
- }
-
- private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
- return new IWeakEscrowTokenRemovedListener.Stub() {
- @Override
- public void onWeakEscrowTokenRemoved(long handle, int userId) {
- // Do nothing.
- }
- };
- }
-}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2de305f8e925..28734283e9b7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -450,7 +450,7 @@ applications that come with the platform
<!-- Permissions required for CTS test - android.server.biometrics -->
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
- <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+ <permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
<permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
@@ -642,5 +642,6 @@ applications that come with the platform
<privapp-permissions package="com.android.devicediagnostics">
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
+ <permission name="android.permission.BATTERY_STATS"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index c77004d4eb17..da91a964565f 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1867,6 +1867,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/ActivityTaskSupervisor.java"
},
+ "-483957611": {
+ "message": "Resuming configuration dispatch for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-481924678": {
"message": "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w.isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d",
"level": "DEBUG",
@@ -4021,6 +4027,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "1473051122": {
+ "message": "Pausing configuration dispatch for %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"1494644409": {
"message": " Rejecting as detached: %s",
"level": "VERBOSE",
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 471acaa0b1b6..f1a6b6974b06 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -80,3 +80,9 @@ prebuilt_fonts_xml {
},
},
}
+
+/////////////////////////////////
+// Move `fontchain_lint` to `core/tasks/fontchain_lint.mk`.
+// Because `system.img` is a dependency of `fontchain_lint`, it cannot be
+// converted to Android.bp for now.
+// After system.img can be generated by Soong, then it can be converted to Android.bp.
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
deleted file mode 100644
index a322b829932b..000000000000
--- a/data/fonts/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2011 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.
-
-LOCAL_PATH := $(call my-dir)
-
-# Run sanity tests on fonts on checkbuild
-checkbuild: fontchain_lint
-
-FONTCHAIN_LINTER := $(HOST_OUT_EXECUTABLES)/fontchain_linter
-ifeq ($(MINIMAL_FONT_FOOTPRINT),true)
-CHECK_EMOJI := false
-else
-CHECK_EMOJI := true
-endif
-
-fontchain_lint_timestamp := $(call intermediates-dir-for,PACKAGING,fontchain_lint)/stamp
-
-.PHONY: fontchain_lint
-fontchain_lint: $(fontchain_lint_timestamp)
-
-fontchain_lint_deps := \
- external/unicode/DerivedAge.txt \
- external/unicode/emoji-data.txt \
- external/unicode/emoji-sequences.txt \
- external/unicode/emoji-variation-sequences.txt \
- external/unicode/emoji-zwj-sequences.txt \
- external/unicode/additions/emoji-data.txt \
- external/unicode/additions/emoji-sequences.txt \
- external/unicode/additions/emoji-zwj-sequences.txt \
-
-$(fontchain_lint_timestamp): $(FONTCHAIN_LINTER) $(TARGET_OUT)/etc/fonts.xml $(PRODUCT_OUT)/system.img $(fontchain_lint_deps)
- @echo Running fontchain lint
- $(FONTCHAIN_LINTER) $(TARGET_OUT) $(CHECK_EMOJI) external/unicode
- touch $@
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 14a46778810c..b2e5b75cf0b5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -42,10 +42,10 @@ import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceh
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
-import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
+import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.annotation.CallbackExecutor;
@@ -574,7 +574,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction();
final WindowContainerTransaction wct = transactionRecord.getTransaction();
- mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
transactionRecord.apply(false /* shouldApplyIndependently */);
}
}
@@ -1567,7 +1568,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private TaskFragmentContainer createEmptyExpandedContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
@Nullable Activity launchingActivity) {
- return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+ return createEmptyContainer(wct, intent, taskId,
+ new ActivityStackAttributes.Builder().build(), launchingActivity,
null /* overlayTag */, null /* launchOptions */);
}
@@ -1581,8 +1583,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable
TaskFragmentContainer createEmptyContainer(
@NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
- @NonNull Rect bounds, @Nullable Activity launchingActivity,
- @Nullable String overlayTag, @Nullable Bundle launchOptions) {
+ @NonNull ActivityStackAttributes activityStackAttributes,
+ @Nullable Activity launchingActivity, @Nullable String overlayTag,
+ @Nullable Bundle launchOptions) {
// We need an activity in the organizer process in the same Task to use as the owner
// activity, as well as to get the Task window info.
final Activity activityInTask;
@@ -1605,43 +1608,21 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Note that taskContainer will not exist before calling #newContainer if the container
// is the first embedded TF in the task.
final TaskContainer taskContainer = container.getTaskContainer();
- final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
- final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+ // TODO(b/265271880): remove redundant logic after all TF operations take fragmentToken.
+ final Rect taskBounds = taskContainer.getBounds();
+ final Rect sanitizedBounds = sanitizeBounds(activityStackAttributes.getRelativeBounds(),
+ getMinDimensions(intent), taskBounds);
final int windowingMode = taskContainer
.getWindowingModeForTaskFragment(sanitizedBounds);
mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
sanitizedBounds, windowingMode);
- mPresenter.updateAnimationParams(wct, taskFragmentToken,
- TaskFragmentAnimationParams.DEFAULT);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
- overlayTag != null && !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, container, activityStackAttributes,
+ getMinDimensions(intent));
return container;
}
/**
- * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
- * covered by the task bounds. Otherwise, returns {@code bounds}.
- */
- @NonNull
- private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
- @NonNull Rect taskBounds) {
- if (bounds.isEmpty()) {
- // Don't need to check if the bounds follows the task bounds.
- return bounds;
- }
- if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
- // Expand the bounds if the bounds are smaller than minimum dimensions.
- return new Rect();
- }
- if (!taskBounds.contains(bounds)) {
- // Expand the bounds if the bounds exceed the task bounds.
- return new Rect();
- }
- return bounds;
- }
-
- /**
* Returns a container for the new activity intent to launch into as splitting with the primary
* activity.
*/
@@ -1958,6 +1939,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return;
}
+ if (mActivityStackAttributesCalculator == null) {
+ Log.e(TAG, "ActivityStackAttributesCalculator is not set. Thus the overlay container"
+ + " can not be updated.");
+ return;
+ }
+
if (mActivityStackAttributesCalculator != null) {
final ActivityStackAttributesCalculatorParams params =
new ActivityStackAttributesCalculatorParams(
@@ -1967,7 +1954,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
container.getLaunchOptions());
final ActivityStackAttributes attributes = mActivityStackAttributesCalculator
.apply(params);
- mPresenter.applyActivityStackAttributes(wct, container, attributes);
+ mPresenter.applyActivityStackAttributes(wct, container, attributes,
+ container.getMinDimensions());
}
}
@@ -2603,15 +2591,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mPresenter.createParentContainerInfoFromTaskProperties(
mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
// Fallback to expand the bounds if there's no activityStackAttributes calculator.
- final Rect relativeBounds = mActivityStackAttributesCalculator != null
- ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
- : new Rect();
- final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds,
- getMinDimensions(intent));
- // Expand the bounds if the requested bounds are smaller than minimum dimensions.
- if (shouldExpandContainer) {
- relativeBounds.setEmpty();
+ final ActivityStackAttributes attrs;
+ if (mActivityStackAttributesCalculator != null) {
+ attrs = mActivityStackAttributesCalculator.apply(params);
+ } else {
+ attrs = new ActivityStackAttributes.Builder().build();
+ Log.e(TAG, "ActivityStackAttributesCalculator isn't set. Fallback to set overlay "
+ + "container as expected.");
}
+
final int taskId = getTaskId(launchActivity);
if (!overlayContainers.isEmpty()) {
for (final TaskFragmentContainer overlayContainer : overlayContainers) {
@@ -2631,20 +2619,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
if (overlayTag.equals(overlayContainer.getOverlayTag())
&& taskId == overlayContainer.getTaskId()) {
- // If there's an overlay container with the same tag and task ID, we treat
- // the OverlayCreateParams as the update to the container.
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- final TaskContainer taskContainer = overlayContainer.getTaskContainer();
- final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics()
- .getBounds();
- final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
-
- mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
- final int windowingMode = taskContainer
- .getWindowingModeForTaskFragment(sanitizedBounds);
- mPresenter.updateWindowingMode(wct, overlayToken, windowingMode);
- mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayContainer,
- !sanitizedBounds.isEmpty());
+ mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
+ getMinDimensions(intent));
// We can just return the updated overlay container and don't need to
// check other condition since we only have one OverlayCreateParams, and
// if the tag and task are matched, it's impossible to match another task
@@ -2654,7 +2630,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
// Launch the overlay container to the task with taskId.
- return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag,
+ return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
options);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 8b7fd108f031..2f2da8c53db0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -16,8 +16,6 @@
package androidx.window.extensions.embedding;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.PackageManager.MATCH_ALL;
import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
@@ -426,7 +424,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* creation has not been reported from the server yet.
*/
// TODO(b/190433398): Handle resize if the fragment hasn't appeared yet.
- private void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
+ @VisibleForTesting
+ void resizeTaskFragmentIfRegistered(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@Nullable Rect relBounds) {
if (container.getInfo() == null) {
@@ -435,7 +434,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
resizeTaskFragment(wct, container.getTaskFragmentToken(), relBounds);
}
- private void updateTaskFragmentWindowingModeIfRegistered(
+ @VisibleForTesting
+ void updateTaskFragmentWindowingModeIfRegistered(
@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container,
@WindowingMode int windowingMode) {
@@ -579,13 +579,53 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.setCompanionTaskFragment(wct, primary, secondary);
}
- void applyActivityStackAttributes(@NonNull WindowContainerTransaction wct,
- @NonNull TaskFragmentContainer container, @NonNull ActivityStackAttributes attributes) {
- final Rect bounds = attributes.getRelativeBounds();
+ void applyActivityStackAttributes(
+ @NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container,
+ @NonNull ActivityStackAttributes attributes,
+ @Nullable Size minDimensions) {
+ final Rect taskBounds = container.getTaskContainer().getBounds();
+ final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions,
+ taskBounds);
+ final boolean isFillParent = relativeBounds.isEmpty();
+ final boolean isIsolatedNavigated = !isFillParent && container.isOverlay();
+ final boolean dimOnTask = !isFillParent
+ && attributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK
+ && Flags.fullscreenDimFlag();
+ final IBinder fragmentToken = container.getTaskFragmentToken();
+
+ // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds
+ // and WCT#setWindowingMode to take fragmentToken.
+ resizeTaskFragmentIfRegistered(wct, container, relativeBounds);
+ int windowingMode = container.getTaskContainer().getWindowingModeForTaskFragment(
+ relativeBounds);
+ updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode);
+ // Always use default animation for standalone ActivityStack.
+ updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT);
+ setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated);
+ setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
+ }
- resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
- updateWindowingMode(wct, container.getTaskFragmentToken(),
- bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
+ /**
+ * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+ * covered by the task bounds. Otherwise, returns {@code bounds}.
+ */
+ @NonNull
+ static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
+ @NonNull Rect taskBounds) {
+ if (bounds.isEmpty()) {
+ // Don't need to check if the bounds follows the task bounds.
+ return bounds;
+ }
+ if (boundsSmallerThanMinDimensions(bounds, minDimension)) {
+ // Expand the bounds if the bounds are smaller than minimum dimensions.
+ return new Rect();
+ }
+ if (!taskBounds.contains(bounds)) {
+ // Expand the bounds if the bounds exceed the task bounds.
+ return new Rect();
+ }
+ return bounds;
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 71195b6df97e..73109e266905 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -126,6 +126,11 @@ class TaskContainer {
}
@NonNull
+ Rect getBounds() {
+ return mConfiguration.windowConfiguration.getBounds();
+ }
+
+ @NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 4e7b76057b5d..34d43ad56bb4 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -16,14 +16,17 @@
package androidx.window.extensions.embedding;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
+import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -36,7 +39,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -56,6 +58,8 @@ import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.util.Size;
+import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo;
import android.window.WindowContainerTransaction;
@@ -266,62 +270,21 @@ public class OverlayPresentationTest {
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+ public void testSanitizeBounds_smallerThanMinDimens_expandOverlay() {
mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
-
final Rect bounds = new Rect(0, 0, 100, 100);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer =
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).updateWindowingMode(mTransaction, overlayToken,
- WINDOWING_MODE_UNDEFINED);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayContainer,
- false);
+ SplitPresenter.sanitizeBounds(bounds, SplitPresenter.getMinDimensions(mIntent),
+ TASK_BOUNDS);
}
@Test
- public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+ public void testSanitizeBounds_notInTaskBounds_expandOverlay() {
final Rect bounds = new Rect(TASK_BOUNDS);
bounds.offset(10, 10);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
-
- final TaskFragmentContainer overlayContainer =
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
- final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
- assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
- false);
-
- // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
- clearInvocations(mSplitPresenter);
- createOrUpdateOverlayTaskFragmentIfNeeded("test");
-
- verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
- verify(mSplitPresenter).updateWindowingMode(mTransaction,
- overlayToken, WINDOWING_MODE_UNDEFINED);
- verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction,
- overlayContainer, false);
- assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
- .containsExactly(overlayContainer);
+ SplitPresenter.sanitizeBounds(bounds, null, TASK_BOUNDS);
}
@Test
@@ -331,6 +294,7 @@ public class OverlayPresentationTest {
new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
final TaskFragmentContainer overlayContainer =
createOrUpdateOverlayTaskFragmentIfNeeded("test");
+ setupTaskFragmentInfo(overlayContainer, mActivity);
assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
.containsExactly(overlayContainer);
@@ -437,7 +401,7 @@ public class OverlayPresentationTest {
assertThrows(NullPointerException.class, () ->
mSplitController.updateActivityStackAttributes(new Binder(), null));
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
@@ -447,7 +411,7 @@ public class OverlayPresentationTest {
mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
new ActivityStackAttributes.Builder().build());
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
@@ -457,19 +421,20 @@ public class OverlayPresentationTest {
mSplitController.updateActivityStackAttributes(container.getTaskFragmentToken(),
new ActivityStackAttributes.Builder().build());
- verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any());
+ verify(mSplitPresenter, never()).applyActivityStackAttributes(any(), any(), any(), any());
}
@Test
public void testUpdateActivityStackAttributes() {
final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test");
- doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any());
+ doNothing().when(mSplitPresenter).applyActivityStackAttributes(any(), any(), any(), any());
final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder().build();
final IBinder token = container.getTaskFragmentToken();
mSplitController.updateActivityStackAttributes(token, attrs);
- verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
+ verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs),
+ any());
}
@Test
@@ -521,6 +486,89 @@ public class OverlayPresentationTest {
.that(taskContainer.getOverlayContainer()).isNull();
}
+ @Test
+ public void testApplyActivityStackAttributesForExpandedContainer() {
+ final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 200, 200))
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_MULTI_WINDOW);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForExpandedOverlayContainer() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder().build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ null /* minDimensions */);
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ attributes.getRelativeBounds());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
+ @Test
+ public void testApplyActivityStackAttributesForOverlayContainer_exceedsMinDimensions() {
+ final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, TEST_TAG);
+ final IBinder token = container.getTaskFragmentToken();
+ final Rect relativeBounds = new Rect(0, 0, 200, 200);
+ final ActivityStackAttributes attributes = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(relativeBounds)
+ .setWindowAttributes(new WindowAttributes(DIM_AREA_ON_TASK))
+ .build();
+
+ mSplitPresenter.applyActivityStackAttributes(mTransaction, container, attributes,
+ new Size(relativeBounds.width() + 1, relativeBounds.height()));
+
+ verify(mSplitPresenter).resizeTaskFragmentIfRegistered(mTransaction, container,
+ new Rect());
+ verify(mSplitPresenter).updateTaskFragmentWindowingModeIfRegistered(mTransaction, container,
+ WINDOWING_MODE_UNDEFINED);
+ verify(mSplitPresenter).updateAnimationParams(mTransaction, token,
+ TaskFragmentAnimationParams.DEFAULT);
+ verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false);
+ verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false);
+ }
+
/**
* A simplified version of {@link SplitController.ActivityStartMonitor
* #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Shell/multivalentTests/OWNERS b/libs/WindowManager/Shell/multivalentTests/OWNERS
new file mode 100644
index 000000000000..24c1a3a6d400
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/OWNERS
@@ -0,0 +1,4 @@
+atsjenk@google.com
+liranb@google.com
+madym@google.com
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
index 1b1ebc39b558..4cbb78f2dae2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDoubleTapHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package com.android.wm.shell.pip.phone;
+package com.android.wm.shell.common.pip;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.graphics.Rect;
-import com.android.wm.shell.common.pip.PipBoundsState;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -50,9 +48,9 @@ public class PipDoubleTapHelper {
@Retention(RetentionPolicy.SOURCE)
@interface PipSizeSpec {}
- static final int SIZE_SPEC_DEFAULT = 0;
- static final int SIZE_SPEC_MAX = 1;
- static final int SIZE_SPEC_CUSTOM = 2;
+ public static final int SIZE_SPEC_DEFAULT = 0;
+ public static final int SIZE_SPEC_MAX = 1;
+ public static final int SIZE_SPEC_CUSTOM = 2;
/**
* Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
@@ -84,7 +82,7 @@ public class PipDoubleTapHelper {
* @return pip screen size to switch to
*/
@PipSizeSpec
- static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
+ public static int nextSizeSpec(@NonNull PipBoundsState mPipBoundsState,
@NonNull Rect userResizeBounds) {
// is pip screen at its maximum
boolean isScreenMax = mPipBoundsState.getBounds().width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 2dd27430e348..dbf7186def8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -80,8 +80,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
- && shouldShowSizeCompatRestartButton(taskInfo);
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
@@ -106,7 +105,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@Override
protected boolean eligibleToShowLayout() {
- return mHasSizeCompat || shouldShowCameraControl();
+ return (mHasSizeCompat && shouldShowSizeCompatRestartButton(getLastTaskInfo()))
+ || shouldShowCameraControl();
}
@Override
@@ -114,11 +114,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
mLayout = inflateLayout();
mLayout.inject(this);
- final TaskInfo taskInfo = getLastTaskInfo();
- if (taskInfo != null) {
- mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo);
- }
-
updateVisibilityOfViews();
if (mHasSizeCompat) {
@@ -139,8 +134,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
- && shouldShowSizeCompatRestartButton(taskInfo);
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
index 180498c50c78..0564c95aef5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManagerAbstract.java
@@ -332,7 +332,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
updateSurfacePosition();
}
- @Nullable
+ @NonNull
protected TaskInfo getLastTaskInfo() {
return mTaskInfo;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 3b48c67a5bbd..7b98fa6523cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -50,15 +50,16 @@ import java.util.Optional;
public abstract class Pip2Module {
@WMSingleton
@Provides
- static PipTransition providePipTransition(@NonNull ShellInit shellInit,
+ static PipTransition providePipTransition(Context context,
+ @NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
Optional<PipController> pipController,
@NonNull PipScheduler pipScheduler) {
- return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
- pipBoundsAlgorithm, pipScheduler);
+ return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
+ pipBoundsState, null, pipBoundsAlgorithm, pipScheduler);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 04911c0bc064..0e7073688ec4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -47,6 +47,7 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
/**
* Responsible supplying PiP Transitions.
@@ -116,6 +117,17 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
+ * Called when the Shell wants to start resizing Pip transition/animation.
+ *
+ * @param onFinishResizeCallback callback guaranteed to execute when animation ends and
+ * client completes any potential draws upon WM state updates.
+ */
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ // Default implementation does nothing.
+ }
+
+ /**
* Called when the transition animation can't continue (eg. task is removed during
* animation)
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 452a41696fcf..81705e20a1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -52,6 +52,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.pip.SizeSpecSource;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 0b8f60e44c7e..57b73b3019f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -24,10 +24,12 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
@@ -36,6 +38,10 @@ import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.PipTransitionController;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.function.Consumer;
+
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -58,13 +64,37 @@ public class PipScheduler {
private SurfaceControl mPinnedTaskLeash;
/**
- * A temporary broadcast receiver to initiate exit PiP via expand.
- * This will later be modified to be triggered by the PiP menu.
+ * Temporary PiP CUJ codes to schedule PiP related transitions directly from Shell.
+ * This is used for a broadcast receiver to resolve intents. This should be removed once
+ * there is an equivalent of PipTouchHandler and PipResizeGestureHandler for PiP2.
+ */
+ private static final int PIP_EXIT_VIA_EXPAND_CODE = 0;
+ private static final int PIP_DOUBLE_TAP = 1;
+
+ @IntDef(value = {
+ PIP_EXIT_VIA_EXPAND_CODE,
+ PIP_DOUBLE_TAP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface PipUserJourneyCode {}
+
+ /**
+ * A temporary broadcast receiver to initiate PiP CUJs.
*/
private class PipSchedulerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- scheduleExitPipViaExpand();
+ int userJourneyCode = intent.getIntExtra("cuj_code_extra", 0);
+ switch (userJourneyCode) {
+ case PIP_EXIT_VIA_EXPAND_CODE:
+ scheduleExitPipViaExpand();
+ break;
+ case PIP_DOUBLE_TAP:
+ scheduleDoubleTapToResize();
+ break;
+ default:
+ throw new IllegalStateException("unexpected CUJ code=" + userJourneyCode);
+ }
}
}
@@ -121,6 +151,23 @@ public class PipScheduler {
}
}
+ /**
+ * Schedules resize PiP via double tap.
+ */
+ public void scheduleDoubleTapToResize() {}
+
+ /**
+ * Animates resizing of the pinned stack given the duration.
+ */
+ public void scheduleAnimateResizePip(Rect toBounds, Consumer<Rect> onFinishResizeCallback) {
+ if (mPipTaskToken == null) {
+ return;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mPipTaskToken, toBounds);
+ mPipTransitionController.startResizeTransition(wct, onFinishResizeCallback);
+ }
+
void onExitPip() {
mPipTaskToken = null;
mPinnedTaskLeash = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 3b0e7c139bed..f3d178aef4ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -22,10 +22,12 @@ import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
+import static com.android.wm.shell.transition.Transitions.TRANSIT_RESIZE_PIP;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
+import android.content.Context;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -36,6 +38,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -45,25 +48,29 @@ import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import java.util.function.Consumer;
+
/**
* Implementation of transitions for PiP on phone.
*/
public class PipTransition extends PipTransitionController {
private static final String TAG = PipTransition.class.getSimpleName();
+ private final Context mContext;
private final PipScheduler mPipScheduler;
@Nullable
private WindowContainerToken mPipTaskToken;
@Nullable
private IBinder mEnterTransition;
@Nullable
- private IBinder mAutoEnterButtonNavTransition;
- @Nullable
private IBinder mExitViaExpandTransition;
@Nullable
- private IBinder mLegacyEnterTransition;
+ private IBinder mResizeTransition;
+
+ private Consumer<Rect> mFinishResizeCallback;
public PipTransition(
+ Context context,
@NonNull ShellInit shellInit,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@NonNull Transitions transitions,
@@ -74,6 +81,7 @@ public class PipTransition extends PipTransitionController {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
+ mContext = context;
mPipScheduler = pipScheduler;
mPipScheduler.setPipTransitionController(this);
}
@@ -87,7 +95,7 @@ public class PipTransition extends PipTransitionController {
@Override
public void startExitTransition(int type, WindowContainerTransaction out,
- @android.annotation.Nullable Rect destinationBounds) {
+ @Nullable Rect destinationBounds) {
if (out == null) {
return;
}
@@ -97,6 +105,16 @@ public class PipTransition extends PipTransitionController {
}
}
+ @Override
+ public void startResizeTransition(WindowContainerTransaction wct,
+ Consumer<Rect> onFinishResizeCallback) {
+ if (wct == null) {
+ return;
+ }
+ mResizeTransition = mTransitions.startTransition(TRANSIT_RESIZE_PIP, wct, this);
+ mFinishResizeCallback = onFinishResizeCallback;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@@ -126,43 +144,6 @@ public class PipTransition extends PipTransitionController {
public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishT) {}
- private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
- @NonNull TransitionRequestInfo request) {
- // cache the original task token to check for multi-activity case later
- final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
- PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
- mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
- pipParams, mPipBoundsAlgorithm);
-
- // calculate the entry bounds and notify core to move task to pinned with final bounds
- final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
- mPipBoundsState.setBounds(entryBounds);
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
- return wct;
- }
-
- private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
- final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
- if (pipTask == null) {
- return false;
- }
- if (pipTask.pictureInPictureParams == null) {
- return false;
- }
-
- // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
- // implies that we are entering PiP in button navigation mode. This is guaranteed by
- // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
- return requestInfo.getType() == TRANSIT_OPEN
- && pipTask.pictureInPictureParams.isAutoEnterEnabled();
- }
-
- private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
- return requestInfo.getType() == TRANSIT_PIP;
- }
-
@Override
public boolean startAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info,
@@ -182,16 +163,48 @@ public class PipTransition extends PipTransitionController {
} else if (transition == mExitViaExpandTransition) {
mExitViaExpandTransition = null;
return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback);
+ } else if (transition == mResizeTransition) {
+ mResizeTransition = null;
+ return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback);
}
return false;
}
- private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+ private boolean startResizeAnimation(@NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
TransitionInfo.Change pipChange = getPipChange(info);
- // If the only change in the changes list is a TO_FRONT mode PiP task,
- // then this is legacy-enter PiP.
- return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
- && info.getChanges().size() == 1;
+ if (pipChange == null) {
+ return false;
+ }
+ SurfaceControl pipLeash = pipChange.getLeash();
+ Rect destinationBounds = pipChange.getEndAbsBounds();
+
+ // Even though the final bounds and crop are applied with finishTransaction since
+ // this is a visible change, we still need to handle the app draw coming in. Snapshot
+ // covering app draw during collection will be removed by startTransaction. So we make
+ // the crop equal to the final bounds and then scale the leash back to starting bounds.
+ startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(),
+ pipChange.getEndAbsBounds().height());
+ startTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+ startTransaction.apply();
+
+ finishTransaction.setScale(pipLeash,
+ (float) mPipBoundsState.getBounds().width() / destinationBounds.width(),
+ (float) mPipBoundsState.getBounds().height() / destinationBounds.height());
+
+ // We are done with the transition, but will continue animating leash to final bounds.
+ finishCallback.onTransitionFinished(null);
+
+ // Animate the pip leash with the new buffer
+ final int duration = mContext.getResources().getInteger(
+ R.integer.config_pipResizeAnimationDuration);
+ // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator.
+ startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration);
+ return true;
}
private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
@@ -251,6 +264,57 @@ public class PipTransition extends PipTransitionController {
return null;
}
+ private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ // cache the original task token to check for multi-activity case later
+ final ActivityManager.RunningTaskInfo pipTask = request.getPipTask();
+ PictureInPictureParams pipParams = pipTask.pictureInPictureParams;
+ mPipBoundsState.setBoundsStateForEntry(pipTask.topActivity, pipTask.topActivityInfo,
+ pipParams, mPipBoundsAlgorithm);
+
+ // calculate the entry bounds and notify core to move task to pinned with final bounds
+ final Rect entryBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ mPipBoundsState.setBounds(entryBounds);
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.movePipActivityToPinnedRootTask(pipTask.token, entryBounds);
+ return wct;
+ }
+
+ private boolean isAutoEnterInButtonNavigation(@NonNull TransitionRequestInfo requestInfo) {
+ final ActivityManager.RunningTaskInfo pipTask = requestInfo.getPipTask();
+ if (pipTask == null) {
+ return false;
+ }
+ if (pipTask.pictureInPictureParams == null) {
+ return false;
+ }
+
+ // Assuming auto-enter is enabled and pipTask is non-null, the TRANSIT_OPEN request type
+ // implies that we are entering PiP in button navigation mode. This is guaranteed by
+ // TaskFragment#startPausing()` in Core which wouldn't get called in gesture nav.
+ return requestInfo.getType() == TRANSIT_OPEN
+ && pipTask.pictureInPictureParams.isAutoEnterEnabled();
+ }
+
+ private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
+ return requestInfo.getType() == TRANSIT_PIP;
+ }
+
+ private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+ TransitionInfo.Change pipChange = getPipChange(info);
+ // If the only change in the changes list is a TO_FRONT mode PiP task,
+ // then this is legacy-enter PiP.
+ return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
+ && info.getChanges().size() == 1;
+ }
+
+ /**
+ * TODO: b/275910498 Use a new implementation of the PiP animator here.
+ */
+ private void startResizeAnimation(SurfaceControl leash, Rect startBounds,
+ Rect endBounds, int duration) {}
+
private void onExitPip() {
mPipTaskToken = null;
mPipScheduler.onExitPip();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index d023cea6d19d..1232baacdac7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1020,7 +1020,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
"RecentsController.finishInner: no valid PiP leash;"
+ "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d",
- mPipTransaction.toString(), mPipTask.toString(), mPipTaskId);
+ mPipTransaction, mPipTask, mPipTaskId);
} else {
t.show(pipLeash);
PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index 253acc49071a..0ca244c4b96a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -158,5 +158,10 @@ interface ISplitScreen {
* does not expect split to currently be running.
*/
RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
+
+ /**
+ * Reverse the split.
+ */
+ oneway void switchSplitPosition() = 22;
}
-// Last id = 21 \ No newline at end of file
+// Last id = 22 \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 2ec52bb028c6..70cb2fc6d52c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -1109,6 +1109,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.onDroppedToSplit(position, dragSessionId);
}
+ void switchSplitPosition(String reason) {
+ if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition(reason);
+ }
+ }
+
/**
* Return the {@param exitReason} as a string.
*/
@@ -1473,5 +1479,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
true /* blocking */);
return out[0];
}
+
+ @Override
+ public void switchSplitPosition() {
+ executeRemoteCallWithTaskPermission(mController, "switchSplitPosition",
+ (controller) -> controller.switchSplitPosition("remoteCall"));
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index 7fd03a9a306b..7f16c5e3592e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -43,6 +43,8 @@ public class SplitScreenShellCommandHandler implements
return runRemoveFromSideStage(args, pw);
case "setSideStagePosition":
return runSetSideStagePosition(args, pw);
+ case "switchSplitPosition":
+ return runSwitchSplitPosition();
default:
pw.println("Invalid command: " + args[0]);
return false;
@@ -84,6 +86,11 @@ public class SplitScreenShellCommandHandler implements
return true;
}
+ private boolean runSwitchSplitPosition() {
+ mController.switchSplitPosition("shellCommand");
+ return true;
+ }
+
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
pw.println(prefix + "moveToSideStage <taskId> <SideStagePosition>");
@@ -92,5 +99,7 @@ public class SplitScreenShellCommandHandler implements
pw.println(prefix + " Remove a task with given id in split-screen mode.");
pw.println(prefix + "setSideStagePosition <SideStagePosition>");
pw.println(prefix + " Sets the position of the side-stage.");
+ pw.println(prefix + "switchSplitPosition");
+ pw.println(prefix + " Reverses the split.");
}
}
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 3fb0dbfaa63d..67fc7e2b4ea6 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
@@ -175,6 +175,9 @@ public class Transitions implements RemoteCallable<Transitions>,
/** Transition to animate task to desktop. */
public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+ /** Transition to resize PiP task. */
+ public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16;
+
private final ShellTaskOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 5a74255df49a..e6faa6391cca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -93,7 +93,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
// On a smaller screen, don't require as much empty space on screen, as offscreen
// drags will be restricted too much.
- final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.taskId)
+ final int requiredEmptySpaceId = mDisplayController.getDisplayContext(mTaskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600
? R.dimen.freeform_required_visible_empty_space_in_header :
R.dimen.small_screen_required_visible_empty_space_in_header;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 554b1fb99550..4ba05ce8aef1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -32,6 +32,7 @@ import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFO
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
@@ -60,7 +61,6 @@ import android.view.ViewConfiguration;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -544,12 +544,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return true;
}
+ /**
+ * Perform a task size toggle on release of the double-tap, assuming no drag event
+ * was handled during the double-tap.
+ * @param e The motion event that occurred during the double-tap gesture.
+ * @return true if the event should be consumed, false if not
+ */
@Override
- public boolean onDoubleTap(@NonNull MotionEvent e) {
+ public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
+ final int action = e.getActionMasked();
+ if (mIsDragging || (action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL)) {
+ return false;
+ }
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
- mDesktopTasksController.ifPresent(c -> {
- c.toggleDesktopTaskSize(taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId));
- });
+ mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo,
+ mWindowDecorByTaskId.get(taskInfo.taskId)));
return true;
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
index 47bff8de377e..0d1853534927 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -78,6 +78,14 @@ abstract class TvPipTestBase : PipTestBase(rotationToString(ROTATION_0), ROTATIO
uiAutomation.dropShellPermissionIdentity()
}
+ override fun onProcessStarted(
+ pid: Int,
+ processUid: Int,
+ packageUid: Int,
+ packageName: String,
+ processName: String
+ ) {}
+
override fun onForegroundActivitiesChanged(pid: Int, uid: Int, foreground: Boolean) {}
override fun onForegroundServicesChanged(pid: Int, uid: Int, serviceTypes: Int) {}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
index 4ddc539eb220..dd358e757fde 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
+import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -83,6 +84,7 @@ public class CompatUILayoutTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -127,7 +129,6 @@ public class CompatUILayoutTest extends ShellTestCase {
@Test
public void testOnClickForSizeCompatHint() {
mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.createLayout(/* canShow= */ true);
final LinearLayout sizeCompatHint = mLayout.findViewById(R.id.size_compat_hint);
sizeCompatHint.performClick();
@@ -222,6 +223,9 @@ public class CompatUILayoutTest extends ShellTestCase {
taskInfo.taskId = TASK_ID;
taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index 2acfd83084ab..4f261cd79d39 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,7 +20,6 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
@@ -86,6 +85,8 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int TASK_ID = 1;
+ private static final int TASK_WIDTH = 2000;
+ private static final int TASK_HEIGHT = 2000;
@Mock private SyncTransactionQueue mSyncTransactionQueue;
@Mock private CompatUIController.CompatUICallback mCallback;
@@ -101,6 +102,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN);
mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
@@ -115,7 +117,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
assertTrue(mWindowManager.createLayout(/* canShow= */ false));
verify(mWindowManager, never()).inflateLayout();
@@ -147,6 +148,13 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mWindowManager.mHasSizeCompat = false;
assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+ // Returns false and doesn't create layout if restart button should be hidden.
+ clearInvocations(mWindowManager);
+ mWindowManager.mHasSizeCompat = true;
+ mTaskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+ mTaskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+ assertFalse(mWindowManager.createLayout(/* canShow= */ true));
+
verify(mWindowManager, never()).inflateLayout();
}
@@ -293,8 +301,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Test
public void testUpdateCompatInfoLayoutNotInflatedYet() {
- mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
mWindowManager.createLayout(/* canShow= */ false);
verify(mWindowManager, never()).inflateLayout();
@@ -314,6 +320,15 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
verify(mWindowManager).inflateLayout();
+
+ // Change shouldShowSizeCompatRestartButton to false and pass canShow true, layout
+ // shouldn't be inflated
+ clearInvocations(mWindowManager);
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = TASK_WIDTH;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT;
+ mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
+
+ verify(mWindowManager, never()).inflateLayout();
}
@Test
@@ -364,7 +379,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// Create button if it is not created.
mWindowManager.mLayout = null;
mWindowManager.mHasSizeCompat = true;
- doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.updateVisibility(/* canShow= */ true);
verify(mWindowManager).createLayout(/* canShow= */ true);
@@ -489,7 +503,6 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
- taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
@@ -514,6 +527,11 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
taskInfo.appCompatTaskInfo.topActivityInSizeCompat = hasSizeCompat;
taskInfo.appCompatTaskInfo.cameraCompatControlState = cameraCompatControlState;
taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
+ // Letterboxed activity that takes half the screen should show size compat restart button
+ taskInfo.configuration.windowConfiguration.setBounds(
+ new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT));
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
index 0f8db85dcef4..b583acda1c9a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipDoubleTapHelperTest.java
@@ -16,10 +16,10 @@
package com.android.wm.shell.pip.phone;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.SIZE_SPEC_MAX;
-import static com.android.wm.shell.pip.phone.PipDoubleTapHelper.nextSizeSpec;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_CUSTOM;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_DEFAULT;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.SIZE_SPEC_MAX;
+import static com.android.wm.shell.common.pip.PipDoubleTapHelper.nextSizeSpec;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -30,6 +30,7 @@ import android.testing.AndroidTestingRunner;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDoubleTapHelper;
import org.junit.Assert;
import org.junit.Before;
@@ -38,7 +39,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
/**
- * Unit test against {@link PipDoubleTapHelper}.
+ * Unit test against {@link com.android.wm.shell.common.pip.PipDoubleTapHelper}.
*/
@RunWith(AndroidTestingRunner.class)
public class PipDoubleTapHelperTest extends ShellTestCase {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 12a5594ae1da..7f3bfbb0e81d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -421,6 +421,15 @@ public class SplitScreenControllerTests extends ShellTestCase {
assertEquals(false, controller.supportsMultiInstanceSplit(component));
}
+ @Test
+ public void testSwitchSplitPosition_checksIsSplitScreenVisible() {
+ final String reason = "test";
+ when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false);
+ mSplitScreenController.switchSplitPosition(reason);
+ mSplitScreenController.switchSplitPosition(reason);
+ verify(mStageCoordinator, times(1)).switchSplitPosition(reason);
+ }
+
private Intent createStartIntent(String activityName) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b40b73c111d0..4e330da417be 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -229,15 +229,6 @@ filegroup {
path: "apex/java",
}
-java_api_contribution {
- name: "framework-graphics-public-stubs",
- api_surface: "public",
- api_file: "api/current.txt",
- visibility: [
- "//build/orchestrator/apis",
- ],
-}
-
// ------------------------
// APEX
// ------------------------
@@ -638,14 +629,6 @@ cc_defaults {
// Allow implicit fallthroughs in HardwareBitmapUploader.cpp until they are fixed.
cflags: ["-Wno-implicit-fallthrough"],
},
- host: {
- srcs: [
- "utils/HostColorSpace.cpp",
- ],
- export_static_lib_headers: [
- "libarect",
- ],
- },
},
}
diff --git a/libs/hwui/utils/HostColorSpace.cpp b/libs/hwui/utils/HostColorSpace.cpp
deleted file mode 100644
index 77a6820c6999..000000000000
--- a/libs/hwui/utils/HostColorSpace.cpp
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-// This is copied from framework/native/libs/ui in order not to include libui in host build
-
-#include <ui/ColorSpace.h>
-
-using namespace std::placeholders;
-
-namespace android {
-
-static constexpr float linearResponse(float v) {
- return v;
-}
-
-static constexpr float rcpResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d * p.c ? (std::pow(x, 1.0f / p.g) - p.b) / p.a : x / p.c;
-}
-
-static constexpr float response(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d ? std::pow(p.a * x + p.b, p.g) : p.c * x;
-}
-
-static constexpr float rcpFullResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d * p.c ? (std::pow(x - p.e, 1.0f / p.g) - p.b) / p.a : (x - p.f) / p.c;
-}
-
-static constexpr float fullResponse(float x, const ColorSpace::TransferParameters& p) {
- return x >= p.d ? std::pow(p.a * x + p.b, p.g) + p.e : p.c * x + p.f;
-}
-
-static float absRcpResponse(float x, float g,float a, float b, float c, float d) {
- float xx = std::abs(x);
- return std::copysign(xx >= d * c ? (std::pow(xx, 1.0f / g) - b) / a : xx / c, x);
-}
-
-static float absResponse(float x, float g, float a, float b, float c, float d) {
- float xx = std::abs(x);
- return std::copysign(xx >= d ? std::pow(a * xx + b, g) : c * xx, x);
-}
-
-static float safePow(float x, float e) {
- return powf(x < 0.0f ? 0.0f : x, e);
-}
-
-static ColorSpace::transfer_function toOETF(const ColorSpace::TransferParameters& parameters) {
- if (parameters.e == 0.0f && parameters.f == 0.0f) {
- return std::bind(rcpResponse, _1, parameters);
- }
- return std::bind(rcpFullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toEOTF( const ColorSpace::TransferParameters& parameters) {
- if (parameters.e == 0.0f && parameters.f == 0.0f) {
- return std::bind(response, _1, parameters);
- }
- return std::bind(fullResponse, _1, parameters);
-}
-
-static ColorSpace::transfer_function toOETF(float gamma) {
- if (gamma == 1.0f) {
- return linearResponse;
- }
- return std::bind(safePow, _1, 1.0f / gamma);
-}
-
-static ColorSpace::transfer_function toEOTF(float gamma) {
- if (gamma == 1.0f) {
- return linearResponse;
- }
- return std::bind(safePow, _1, gamma);
-}
-
-static constexpr std::array<float2, 3> computePrimaries(const mat3& rgbToXYZ) {
- float3 r(rgbToXYZ * float3{1, 0, 0});
- float3 g(rgbToXYZ * float3{0, 1, 0});
- float3 b(rgbToXYZ * float3{0, 0, 1});
-
- return {{r.xy / dot(r, float3{1}),
- g.xy / dot(g, float3{1}),
- b.xy / dot(b, float3{1})}};
-}
-
-static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) {
- float3 w(rgbToXYZ * float3{1});
- return w.xy / dot(w, float3{1});
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- transfer_function OETF,
- transfer_function EOTF,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mOETF(std::move(OETF))
- , mEOTF(std::move(EOTF))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- const TransferParameters parameters,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mParameters(parameters)
- , mOETF(toOETF(mParameters))
- , mEOTF(toEOTF(mParameters))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const mat3& rgbToXYZ,
- float gamma,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(rgbToXYZ)
- , mXYZtoRGB(inverse(rgbToXYZ))
- , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
- , mOETF(toOETF(gamma))
- , mEOTF(toEOTF(gamma))
- , mClamper(std::move(clamper))
- , mPrimaries(computePrimaries(rgbToXYZ))
- , mWhitePoint(computeWhitePoint(rgbToXYZ)) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- transfer_function OETF,
- transfer_function EOTF,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mOETF(std::move(OETF))
- , mEOTF(std::move(EOTF))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- const TransferParameters parameters,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mParameters(parameters)
- , mOETF(toOETF(mParameters))
- , mEOTF(toEOTF(mParameters))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-ColorSpace::ColorSpace(
- const std::string& name,
- const std::array<float2, 3>& primaries,
- const float2& whitePoint,
- float gamma,
- clamping_function clamper) noexcept
- : mName(name)
- , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint))
- , mXYZtoRGB(inverse(mRGBtoXYZ))
- , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f})
- , mOETF(toOETF(gamma))
- , mEOTF(toEOTF(gamma))
- , mClamper(std::move(clamper))
- , mPrimaries(primaries)
- , mWhitePoint(whitePoint) {
-}
-
-constexpr mat3 ColorSpace::computeXYZMatrix(
- const std::array<float2, 3>& primaries, const float2& whitePoint) {
- const float2& R = primaries[0];
- const float2& G = primaries[1];
- const float2& B = primaries[2];
- const float2& W = whitePoint;
-
- float oneRxRy = (1 - R.x) / R.y;
- float oneGxGy = (1 - G.x) / G.y;
- float oneBxBy = (1 - B.x) / B.y;
- float oneWxWy = (1 - W.x) / W.y;
-
- float RxRy = R.x / R.y;
- float GxGy = G.x / G.y;
- float BxBy = B.x / B.y;
- float WxWy = W.x / W.y;
-
- float BY =
- ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
- ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
- float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
- float RY = 1 - GY - BY;
-
- float RYRy = RY / R.y;
- float GYGy = GY / G.y;
- float BYBy = BY / B.y;
-
- return {
- float3{RYRy * R.x, RY, RYRy * (1 - R.x - R.y)},
- float3{GYGy * G.x, GY, GYGy * (1 - G.x - G.y)},
- float3{BYBy * B.x, BY, BYBy * (1 - B.x - B.y)}
- };
-}
-
-const ColorSpace ColorSpace::sRGB() {
- return {
- "sRGB IEC61966-2.1",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::linearSRGB() {
- return {
- "sRGB IEC61966-2.1 (Linear)",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f}
- };
-}
-
-const ColorSpace ColorSpace::extendedSRGB() {
- return {
- "scRGB-nl IEC 61966-2-2:2003",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- std::bind(absRcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
- std::bind(absResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f),
- std::bind(clamp<float>, _1, -0.799f, 2.399f)
- };
-}
-
-const ColorSpace ColorSpace::linearExtendedSRGB() {
- return {
- "scRGB IEC 61966-2-2:2003",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- 1.0f,
- std::bind(clamp<float>, _1, -0.5f, 7.499f)
- };
-}
-
-const ColorSpace ColorSpace::NTSC() {
- return {
- "NTSC (1953)",
- {{float2{0.67f, 0.33f}, {0.21f, 0.71f}, {0.14f, 0.08f}}},
- {0.310f, 0.316f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::BT709() {
- return {
- "Rec. ITU-R BT.709-5",
- {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::BT2020() {
- return {
- "Rec. ITU-R BT.2020-1",
- {{float2{0.708f, 0.292f}, {0.170f, 0.797f}, {0.131f, 0.046f}}},
- {0.3127f, 0.3290f},
- {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::AdobeRGB() {
- return {
- "Adobe RGB (1998)",
- {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}},
- {0.3127f, 0.3290f},
- 2.2f
- };
-}
-
-const ColorSpace ColorSpace::ProPhotoRGB() {
- return {
- "ROMM RGB ISO 22028-2:2013",
- {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}},
- {0.34567f, 0.35850f},
- {1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::DisplayP3() {
- return {
- "Display P3",
- {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
- {0.3127f, 0.3290f},
- {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f, 0.0f, 0.0f}
- };
-}
-
-const ColorSpace ColorSpace::DCIP3() {
- return {
- "SMPTE RP 431-2-2007 DCI (P3)",
- {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}},
- {0.314f, 0.351f},
- 2.6f
- };
-}
-
-const ColorSpace ColorSpace::ACES() {
- return {
- "SMPTE ST 2065-1:2012 ACES",
- {{float2{0.73470f, 0.26530f}, {0.0f, 1.0f}, {0.00010f, -0.0770f}}},
- {0.32168f, 0.33767f},
- 1.0f,
- std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
- };
-}
-
-const ColorSpace ColorSpace::ACEScg() {
- return {
- "Academy S-2014-004 ACEScg",
- {{float2{0.713f, 0.293f}, {0.165f, 0.830f}, {0.128f, 0.044f}}},
- {0.32168f, 0.33767f},
- 1.0f,
- std::bind(clamp<float>, _1, -65504.0f, 65504.0f)
- };
-}
-
-std::unique_ptr<float3[]> ColorSpace::createLUT(uint32_t size, const ColorSpace& src,
- const ColorSpace& dst) {
- size = clamp(size, 2u, 256u);
- float m = 1.0f / float(size - 1);
-
- std::unique_ptr<float3[]> lut(new float3[size * size * size]);
- float3* data = lut.get();
-
- ColorSpaceConnector connector(src, dst);
-
- for (uint32_t z = 0; z < size; z++) {
- for (int32_t y = int32_t(size - 1); y >= 0; y--) {
- for (uint32_t x = 0; x < size; x++) {
- *data++ = connector.transform({x * m, y * m, z * m});
- }
- }
- }
-
- return lut;
-}
-
-static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f};
-static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f};
-static const mat3 BRADFORD = mat3{
- float3{ 0.8951f, -0.7502f, 0.0389f},
- float3{ 0.2664f, 1.7135f, -0.0685f},
- float3{-0.1614f, 0.0367f, 1.0296f}
-};
-
-static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) {
- float3 srcLMS = matrix * srcWhitePoint;
- float3 dstLMS = matrix * dstWhitePoint;
- return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix;
-}
-
-ColorSpaceConnector::ColorSpaceConnector(
- const ColorSpace& src,
- const ColorSpace& dst) noexcept
- : mSource(src)
- , mDestination(dst) {
-
- if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) {
- mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ();
- } else {
- mat3 rgbToXYZ(src.getRGBtoXYZ());
- mat3 xyzToRGB(dst.getXYZtoRGB());
-
- float3 srcXYZ = ColorSpace::XYZ(float3{src.getWhitePoint(), 1});
- float3 dstXYZ = ColorSpace::XYZ(float3{dst.getWhitePoint(), 1});
-
- if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
- rgbToXYZ = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ();
- }
-
- if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) {
- xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ());
- }
-
- mTransform = xyzToRGB * rgbToXYZ;
- }
-}
-
-}; // namespace android
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
index 3dc024efef56..6f8891216bed 100644
--- a/location/java/android/location/altitude/AltitudeConverter.java
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -19,9 +19,11 @@ package android.location.altitude;
import android.annotation.NonNull;
import android.annotation.WorkerThread;
import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
import android.location.Location;
-import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.GeoidMap;
import com.android.internal.location.altitude.S2CellIdUtils;
import com.android.internal.location.altitude.nano.MapParamsProto;
import com.android.internal.util.Preconditions;
@@ -37,7 +39,7 @@ import java.io.IOException;
* <pre>
* Brian Julian and Michael Angermann.
* "Resource efficient and accurate altitude conversion to Mean Sea Level."
- * To appear in 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
+ * 2023 IEEE/ION Position, Location and Navigation Symposium (PLANS).
* </pre>
*/
public final class AltitudeConverter {
@@ -45,8 +47,8 @@ public final class AltitudeConverter {
private static final double MAX_ABS_VALID_LATITUDE = 90;
private static final double MAX_ABS_VALID_LONGITUDE = 180;
- /** Manages a mapping of geoid heights associated with S2 cells. */
- private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+ /** Manages a mapping of geoid heights and expiration distances associated with S2 cells. */
+ private final GeoidMap mGeoidMap = new GeoidMap();
/**
* Creates an instance that manages an independent cache to optimized conversions of locations
@@ -78,75 +80,87 @@ public final class AltitudeConverter {
/**
* Returns the four S2 cell IDs for the map square associated with the {@code location}.
*
- * <p>The first map cell contains the location, while the others are located horizontally,
- * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
- * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
- * corresponding ID is set to zero.
+ * <p>The first map cell, denoted z11 in the appendix of the referenced paper above, contains
+ * the location. The others are the map cells denoted z21, z12, and z22, in that order.
*/
- @NonNull
- private static long[] findMapSquare(@NonNull MapParamsProto params,
+ private static long[] findMapSquare(@NonNull MapParamsProto geoidHeightParams,
@NonNull Location location) {
long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
location.getLongitude());
// Cell-space properties and coordinates.
- int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ int sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
int maxIj = 1 << S2CellIdUtils.MAX_LEVEL;
- long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
- int f0 = S2CellIdUtils.getFace(s2CellId);
- int i0 = S2CellIdUtils.getI(s2CellId);
- int j0 = S2CellIdUtils.getJ(s2CellId);
- int i1 = i0 + sizeIj;
- int j1 = j0 + sizeIj;
+ long z11 = S2CellIdUtils.getParent(s2CellId, geoidHeightParams.mapS2Level);
+ int f11 = S2CellIdUtils.getFace(s2CellId);
+ int i1 = S2CellIdUtils.getI(s2CellId);
+ int j1 = S2CellIdUtils.getJ(s2CellId);
+ int i2 = i1 + sizeIj;
+ int j2 = j1 + sizeIj;
// Non-boundary region calculation - simplest and most common case.
- if (i1 < maxIj && j1 < maxIj) {
- return new long[]{
- s0,
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j0), params.mapS2Level),
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i0, j1), params.mapS2Level),
- S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f0, i1, j1), params.mapS2Level)
- };
+ if (i2 < maxIj && j2 < maxIj) {
+ return new long[]{z11, S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j1),
+ geoidHeightParams.mapS2Level), S2CellIdUtils.getParent(
+ S2CellIdUtils.fromFij(f11, i1, j2), geoidHeightParams.mapS2Level),
+ S2CellIdUtils.getParent(S2CellIdUtils.fromFij(f11, i2, j2),
+ geoidHeightParams.mapS2Level)};
}
- // Boundary region calculation.
+ // Boundary region calculation
long[] edgeNeighbors = new long[4];
- S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
- long s1 = edgeNeighbors[1];
- long s2 = edgeNeighbors[2];
- long s3;
- if (f0 % 2 == 1) {
- S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
- if (i1 < maxIj) {
- s3 = edgeNeighbors[2];
- } else {
- s3 = s1;
- s1 = edgeNeighbors[1];
- }
- } else {
- S2CellIdUtils.getEdgeNeighbors(s2, edgeNeighbors);
- if (j1 < maxIj) {
- s3 = edgeNeighbors[1];
- } else {
- s3 = s2;
- s2 = edgeNeighbors[3];
- }
- }
+ S2CellIdUtils.getEdgeNeighbors(z11, edgeNeighbors);
+ long z11W = edgeNeighbors[0];
+ long z11S = edgeNeighbors[1];
+ long z11E = edgeNeighbors[2];
+ long z11N = edgeNeighbors[3];
+
+ long[] otherEdgeNeighbors = new long[4];
+ S2CellIdUtils.getEdgeNeighbors(z11W, otherEdgeNeighbors);
+ S2CellIdUtils.getEdgeNeighbors(z11S, edgeNeighbors);
+ long z11Sw = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+ S2CellIdUtils.getEdgeNeighbors(z11E, otherEdgeNeighbors);
+ long z11Se = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+ S2CellIdUtils.getEdgeNeighbors(z11N, edgeNeighbors);
+ long z11Ne = findCommonNeighbor(edgeNeighbors, otherEdgeNeighbors, z11);
+
+ long z21 = (f11 % 2 == 1 && i2 >= maxIj) ? z11Sw : z11S;
+ long z12 = (f11 % 2 == 0 && j2 >= maxIj) ? z11Ne : z11E;
+ long z22 = (z21 == z11Sw) ? z11S : (z12 == z11Ne) ? z11E : z11Se;
// Reuse edge neighbors' array to avoid an extra allocation.
- edgeNeighbors[0] = s0;
- edgeNeighbors[1] = s1;
- edgeNeighbors[2] = s2;
- edgeNeighbors[3] = s3;
+ edgeNeighbors[0] = z11;
+ edgeNeighbors[1] = z21;
+ edgeNeighbors[2] = z12;
+ edgeNeighbors[3] = z22;
return edgeNeighbors;
}
/**
+ * Returns the first common non-z11 neighbor found between the two arrays of edge neighbors. If
+ * such a common neighbor does not exist, returns z11.
+ */
+ private static long findCommonNeighbor(long[] edgeNeighbors, long[] otherEdgeNeighbors,
+ long z11) {
+ for (long edgeNeighbor : edgeNeighbors) {
+ if (edgeNeighbor == z11) {
+ continue;
+ }
+ for (long otherEdgeNeighbor : otherEdgeNeighbors) {
+ if (edgeNeighbor == otherEdgeNeighbor) {
+ return edgeNeighbor;
+ }
+ }
+ }
+ return z11;
+ }
+
+ /**
* Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
* Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
* accuracy; otherwise, does not add a corresponding accuracy.
*/
- private static void addMslAltitude(@NonNull MapParamsProto params,
+ private static void addMslAltitude(@NonNull MapParamsProto geoidHeightParams,
@NonNull double[] geoidHeightsMeters, @NonNull Location location) {
double h0 = geoidHeightsMeters[0];
double h1 = geoidHeightsMeters[1];
@@ -158,7 +172,7 @@ public final class AltitudeConverter {
// employ the simplified unit square formulation.
long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
location.getLongitude());
- double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+ double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - geoidHeightParams.mapS2Level);
double wi = (S2CellIdUtils.getI(s2CellId) % sizeIj) / sizeIj;
double wj = (S2CellIdUtils.getJ(s2CellId) % sizeIj) / sizeIj;
double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
@@ -167,8 +181,8 @@ public final class AltitudeConverter {
if (location.hasVerticalAccuracy()) {
double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
- location.setMslAltitudeAccuracyMeters(
- (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+ location.setMslAltitudeAccuracyMeters((float) Math.hypot(verticalAccuracyMeters,
+ geoidHeightParams.modelRmseMeters));
}
}
}
@@ -191,10 +205,11 @@ public final class AltitudeConverter {
public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location)
throws IOException {
validate(location);
- MapParamsProto params = GeoidHeightMap.getParams(context);
- long[] s2CellIds = findMapSquare(params, location);
- double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
- addMslAltitude(params, geoidHeightsMeters, location);
+ MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams(context);
+ long[] mapCells = findMapSquare(geoidHeightParams, location);
+ double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, context,
+ mapCells);
+ addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
}
/**
@@ -206,18 +221,68 @@ public final class AltitudeConverter {
*/
public boolean addMslAltitudeToLocation(@NonNull Location location) {
validate(location);
- MapParamsProto params = GeoidHeightMap.getParams();
- if (params == null) {
+ MapParamsProto geoidHeightParams = GeoidMap.getGeoidHeightParams();
+ if (geoidHeightParams == null) {
return false;
}
- long[] s2CellIds = findMapSquare(params, location);
- double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, s2CellIds);
+ long[] mapCells = findMapSquare(geoidHeightParams, location);
+ double[] geoidHeightsMeters = mGeoidMap.readGeoidHeights(geoidHeightParams, mapCells);
if (geoidHeightsMeters == null) {
return false;
}
- addMslAltitude(params, geoidHeightsMeters, location);
+ addMslAltitude(geoidHeightParams, geoidHeightsMeters, location);
return true;
}
+
+ /**
+ * Returns the geoid height (a.k.a. geoid undulation) at the location specified in {@code
+ * request}. The geoid height at a location is defined as the difference between an altitude
+ * measured above the World Geodetic System 1984 reference ellipsoid (WGS84) and its
+ * corresponding Mean Sea Level altitude.
+ *
+ * <p>Must be called off the main thread as data may be loaded from raw assets.
+ *
+ * @throws IOException if an I/O error occurs when loading data from raw assets.
+ * @throws IllegalArgumentException if the {@code request} has an invalid latitude or longitude.
+ * Specifically, the latitude must be between -90 and 90 (both
+ * inclusive), and the longitude must be between -180 and 180
+ * (both inclusive).
+ * @hide
+ */
+ @WorkerThread
+ public @NonNull GetGeoidHeightResponse getGeoidHeight(@NonNull Context context,
+ @NonNull GetGeoidHeightRequest request) throws IOException {
+ // Create a valid location from which the geoid height and its accuracy will be extracted.
+ Location location = new Location("");
+ location.setLatitude(request.latitudeDegrees);
+ location.setLongitude(request.longitudeDegrees);
+ location.setAltitude(0.0);
+ location.setVerticalAccuracyMeters(0.0f);
+
+ addMslAltitudeToLocation(context, location);
+ // The geoid height for a location with zero WGS84 altitude is equal in value to the
+ // negative of corresponding MSL altitude.
+ double geoidHeightMeters = -location.getMslAltitudeMeters();
+ // The geoid height error for a location with zero vertical accuracy is equal in value to
+ // the corresponding MSL altitude accuracy.
+ float geoidHeightErrorMeters = location.getMslAltitudeAccuracyMeters();
+
+ MapParamsProto expirationDistanceParams = GeoidMap.getExpirationDistanceParams(context);
+ long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+ location.getLongitude());
+ long[] mapCell = {S2CellIdUtils.getParent(s2CellId, expirationDistanceParams.mapS2Level)};
+ double expirationDistanceMeters = mGeoidMap.readExpirationDistances(
+ expirationDistanceParams, context, mapCell)[0];
+ float additionalGeoidHeightErrorMeters = (float) expirationDistanceParams.modelRmseMeters;
+
+ GetGeoidHeightResponse response = new GetGeoidHeightResponse();
+ response.geoidHeightMeters = geoidHeightMeters;
+ response.geoidHeightErrorMeters = geoidHeightErrorMeters;
+ response.expirationDistanceMeters = expirationDistanceMeters;
+ response.additionalGeoidHeightErrorMeters = additionalGeoidHeightErrorMeters;
+ response.success = true;
+ return response;
+ }
}
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidMap.java
index 8067050d9da3..9bf5689c1028 100644
--- a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
+++ b/location/java/com/android/internal/location/altitude/GeoidMap.java
@@ -34,11 +34,12 @@ import java.nio.ByteBuffer;
import java.util.Objects;
/**
- * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ * Manages a mapping of geoid heights and expiration distances associated with S2 cells, referred to
+ * as MAP CELLS.
*
* <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
- * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
- * level.
+ * on disk. A tile associates geoid heights or expiration distances with all map cells of a common
+ * parent at a specified S2 level.
*
* <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
* are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
@@ -48,42 +49,79 @@ import java.util.Objects;
* The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
* referred to as a DISK TOKEN.
*/
-public final class GeoidHeightMap {
+public final class GeoidMap {
- private static final Object sLock = new Object();
+ private static final Object GEOID_HEIGHT_PARAMS_LOCK = new Object();
- @GuardedBy("sLock")
+ private static final Object EXPIRATION_DISTANCE_PARAMS_LOCK = new Object();
+
+ @GuardedBy("GEOID_HEIGHT_PARAMS_LOCK")
+ @Nullable
+ private static MapParamsProto sGeoidHeightParams;
+
+ @GuardedBy("EXPIRATION_DISTANCE_PARAMS_LOCK")
@Nullable
- private static MapParamsProto sParams;
+ private static MapParamsProto sExpirationDistanceParams;
+
+ /**
+ * Defines a cache large enough to hold all geoid height cache tiles needed for interpolation.
+ */
+ private final LruCache<Long, S2TileProto> mGeoidHeightCacheTiles = new LruCache<>(4);
- /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
- private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+ /**
+ * Defines a cache large enough to hold all expiration distance cache tiles needed for
+ * interpolation.
+ */
+ private final LruCache<Long, S2TileProto> mExpirationDistanceCacheTiles = new LruCache<>(4);
/**
- * Returns the singleton parameter instance for a spherically projected geoid height map and its
- * corresponding tile management.
+ * Returns the singleton parameter instance for geoid height parameters of a spherically
+ * projected map.
*/
@NonNull
- public static MapParamsProto getParams(@NonNull Context context) throws IOException {
- synchronized (sLock) {
- if (sParams == null) {
- try (InputStream is = context.getApplicationContext().getAssets().open(
- "geoid_height_map/map-params.pb")) {
- sParams = MapParamsProto.parseFrom(is.readAllBytes());
- }
+ public static MapParamsProto getGeoidHeightParams(@NonNull Context context) throws IOException {
+ synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+ if (sGeoidHeightParams == null) {
+ // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+ sGeoidHeightParams = parseParams(context);
+ }
+ return sGeoidHeightParams;
+ }
+ }
+
+ /**
+ * Returns the singleton parameter instance for expiration distance parameters of a spherically
+ * projected
+ * map.
+ */
+ @NonNull
+ public static MapParamsProto getExpirationDistanceParams(@NonNull Context context)
+ throws IOException {
+ synchronized (EXPIRATION_DISTANCE_PARAMS_LOCK) {
+ if (sExpirationDistanceParams == null) {
+ // TODO: b/304375846 - Configure with disk tile prefix once resources are updated.
+ sExpirationDistanceParams = parseParams(context);
}
- return sParams;
+ return sExpirationDistanceParams;
+ }
+ }
+
+ @NonNull
+ private static MapParamsProto parseParams(@NonNull Context context) throws IOException {
+ try (InputStream is = context.getApplicationContext().getAssets().open(
+ "geoid_height_map/map-params.pb")) {
+ return MapParamsProto.parseFrom(is.readAllBytes());
}
}
/**
- * Same as {@link #getParams(Context)} except that null is returned if the singleton parameter
- * instance is not yet initialized.
+ * Same as {@link #getGeoidHeightParams(Context)} except that null is returned if the singleton
+ * parameter instance is not yet initialized.
*/
@Nullable
- public static MapParamsProto getParams() {
- synchronized (sLock) {
- return sParams;
+ public static MapParamsProto getGeoidHeightParams() {
+ synchronized (GEOID_HEIGHT_PARAMS_LOCK) {
+ return sGeoidHeightParams;
}
}
@@ -93,18 +131,17 @@ public final class GeoidHeightMap {
@NonNull
private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
- return S2CellIdUtils.getToken(
- S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+ return S2CellIdUtils.getToken(S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
}
/**
* Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
- * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, returns false
- * and adds NaNs for absent values.
+ * {@code s2CellIds}. Returns true if values are present for all IDs; otherwise, adds NaNs for
+ * absent values and returns false.
*/
private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
- @NonNull TileFunction tileFunction,
- @NonNull long[] s2CellIds, @NonNull double[] values) {
+ @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+ @NonNull double[] values) {
int len = s2CellIds.length;
S2TileProto[] tiles = new S2TileProto[len];
@@ -137,9 +174,8 @@ public final class GeoidHeightMap {
@SuppressWarnings("ReferenceEquality")
private static void mergeByteBufferValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
byte[] bytes = tiles[tileIndex].byteBuffer;
if (bytes == null || bytes.length == 0) {
return;
@@ -163,24 +199,22 @@ public final class GeoidHeightMap {
}
private static void mergeByteJpegValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
values);
}
private static void mergeBytePngValues(@NonNull MapParamsProto params,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles,
- int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
}
@SuppressWarnings("ReferenceEquality")
private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
- @NonNull long[] s2CellIds,
- @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+ @NonNull long[] s2CellIds, @NonNull S2TileProto[] tiles, int tileIndex,
+ @NonNull double[] values) {
if (bytes == null || bytes.length == 0) {
return;
}
@@ -219,7 +253,7 @@ public final class GeoidHeightMap {
* ID.
*/
private static void validate(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
- Preconditions.checkArgument(s2CellIds.length == 4);
+ Preconditions.checkArgument(s2CellIds.length <= 4);
for (long s2CellId : s2CellIds) {
Preconditions.checkArgument(S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
}
@@ -233,15 +267,38 @@ public final class GeoidHeightMap {
@NonNull
public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
@NonNull long[] s2CellIds) throws IOException {
+ return readMapValues(params, context, s2CellIds, mGeoidHeightCacheTiles);
+ }
+
+ /**
+ * Returns the expiration distances in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for
+ * an ID.
+ */
+ @NonNull
+ public double[] readExpirationDistances(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ return readMapValues(params, context, s2CellIds, mExpirationDistanceCacheTiles);
+ }
+
+ /**
+ * Returns the map values in meters associated with the map cells identified by
+ * {@code s2CellIds}. Throws an {@link IOException} if a map value cannot be calculated for an
+ * ID.
+ */
+ @NonNull
+ private static double[] readMapValues(@NonNull MapParamsProto params, @NonNull Context context,
+ @NonNull long[] s2CellIds, @NonNull LruCache<Long, S2TileProto> cacheTiles)
+ throws IOException {
validate(params, s2CellIds);
- double[] heightsMeters = new double[s2CellIds.length];
- if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
- return heightsMeters;
+ double[] mapValuesMeters = new double[s2CellIds.length];
+ if (getMapValues(params, cacheTiles::get, s2CellIds, mapValuesMeters)) {
+ return mapValuesMeters;
}
- TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
- if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
- return heightsMeters;
+ TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds, cacheTiles);
+ if (getMapValues(params, loadedTiles, s2CellIds, mapValuesMeters)) {
+ return mapValuesMeters;
}
throw new IOException("Unable to calculate geoid heights from raw assets.");
}
@@ -255,32 +312,33 @@ public final class GeoidHeightMap {
public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull long[] s2CellIds) {
validate(params, s2CellIds);
double[] heightsMeters = new double[s2CellIds.length];
- if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+ if (getMapValues(params, mGeoidHeightCacheTiles::get, s2CellIds, heightsMeters)) {
return heightsMeters;
}
return null;
}
/**
- * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+ * Adds to {@code mapValuesMeters} the map values in meters associated with the map cells
* identified by {@code s2CellIds}. Returns true if heights are present for all IDs; otherwise,
- * returns false and adds NaNs for absent heights.
+ * adds NaNs for absent heights and returns false.
*/
- private boolean getGeoidHeights(@NonNull MapParamsProto params,
+ private static boolean getMapValues(@NonNull MapParamsProto params,
@NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
- @NonNull double[] heightsMeters) {
- boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
- for (int i = 0; i < heightsMeters.length; i++) {
+ @NonNull double[] mapValuesMeters) {
+ boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, mapValuesMeters);
+ for (int i = 0; i < mapValuesMeters.length; i++) {
// NaNs are properly preserved.
- heightsMeters[i] *= params.modelAMeters;
- heightsMeters[i] += params.modelBMeters;
+ mapValuesMeters[i] *= params.modelAMeters;
+ mapValuesMeters[i] += params.modelBMeters;
}
return allFound;
}
@NonNull
- private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
- @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+ private static TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+ @NonNull Context context, @NonNull long[] s2CellIds,
+ @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
int len = s2CellIds.length;
// Enable batch loading by finding all cache keys upfront.
@@ -296,7 +354,7 @@ public final class GeoidHeightMap {
if (diskTokens[i] != null) {
continue;
}
- loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+ loadedTiles[i] = cacheTiles.get(cacheKeys[i]);
diskTokens[i] = getDiskToken(params, cacheKeys[i]);
// Batch across common cache key.
@@ -319,7 +377,7 @@ public final class GeoidHeightMap {
"geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
tile = S2TileProto.parseFrom(is.readAllBytes());
}
- mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+ mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles, cacheTiles);
}
return cacheKey -> {
@@ -332,9 +390,10 @@ public final class GeoidHeightMap {
};
}
- private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
- @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
- @NonNull S2TileProto[] loadedTiles) throws IOException {
+ private static void mergeFromDiskTile(@NonNull MapParamsProto params,
+ @NonNull S2TileProto diskTile, @NonNull long[] cacheKeys, @NonNull String[] diskTokens,
+ int diskTokenIndex, @NonNull S2TileProto[] loadedTiles,
+ @NonNull LruCache<Long, S2TileProto> cacheTiles) throws IOException {
int len = cacheKeys.length;
int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
@@ -375,7 +434,7 @@ public final class GeoidHeightMap {
}
// Side load into tile cache.
- mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+ cacheTiles.put(cacheKeys[i], loadedTiles[i]);
}
}
diff --git a/media/java/android/media/LoudnessCodecController.java b/media/java/android/media/LoudnessCodecController.java
index b3e5c52b27b3..61c913166e1c 100644
--- a/media/java/android/media/LoudnessCodecController.java
+++ b/media/java/android/media/LoudnessCodecController.java
@@ -32,12 +32,13 @@ import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
/**
* Class for getting recommended loudness parameter updates for audio decoders as they are used
@@ -320,11 +321,6 @@ public class LoudnessCodecController implements SafeCloseable {
* Stops any loudness updates and frees up the resources.
*/
@FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
- public void release() {
- close();
- }
-
- /** @hide */
@Override
public void close() {
synchronized (mControllerLock) {
@@ -339,9 +335,12 @@ public class LoudnessCodecController implements SafeCloseable {
}
/** @hide */
- /*package*/ Map<LoudnessCodecInfo, Set<MediaCodec>> getRegisteredMediaCodecs() {
+ /*package*/ void mediaCodecsConsume(
+ Consumer<Entry<LoudnessCodecInfo, Set<MediaCodec>>> consumer) {
synchronized (mControllerLock) {
- return mMediaCodecs;
+ for (Entry<LoudnessCodecInfo, Set<MediaCodec>> entry : mMediaCodecs.entrySet()) {
+ consumer.accept(entry);
+ }
}
}
diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java
index 46be54be55ec..fa08658a214f 100644
--- a/media/java/android/media/LoudnessCodecDispatcher.java
+++ b/media/java/android/media/LoudnessCodecDispatcher.java
@@ -32,7 +32,6 @@ import androidx.annotation.NonNull;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
@@ -81,16 +80,15 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
mConfiguratorListener.computeIfPresent(listener, (l, lcConfig) -> {
// send the appropriate bundle for the user to update
if (lcConfig.getSessionId() == sessionId) {
- final Map<LoudnessCodecInfo, Set<MediaCodec>> mediaCodecsMap =
- lcConfig.getRegisteredMediaCodecs();
- for (LoudnessCodecInfo codecInfo : mediaCodecsMap.keySet()) {
+ lcConfig.mediaCodecsConsume(mcEntry -> {
+ final LoudnessCodecInfo codecInfo = mcEntry.getKey();
final String infoKey = Integer.toString(codecInfo.hashCode());
Bundle bundle = null;
if (params.containsKey(infoKey)) {
bundle = new Bundle(params.getPersistableBundle(infoKey));
}
- final Set<MediaCodec> mediaCodecs = mediaCodecsMap.get(codecInfo);
+ final Set<MediaCodec> mediaCodecs = mcEntry.getValue();
for (MediaCodec mediaCodec : mediaCodecs) {
final String mediaCodecKey = Integer.toString(
mediaCodec.hashCode());
@@ -121,7 +119,7 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub {
break;
}
}
- }
+ });
}
return lcConfig;
});
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 691aa7784d7a..425db06ce55f 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -183,23 +183,23 @@ public final class MediaRouter2 {
* preference} passed by a proxy router. Use {@link RouteDiscoveryPreference#EMPTY} when
* setting a route callback.
* <li>
- * <p>Methods returning non-system {@link RoutingController controllers} always return
- * new instances with the latest data. Do not attempt to compare or store them. Instead,
- * use {@link #getController(String)} or {@link #getControllers()} to query the most
+ * <p>Methods returning non-system {@link RoutingController controllers} always return new
+ * instances with the latest data. Do not attempt to compare or store them. Instead, use
+ * {@link #getController(String)} or {@link #getControllers()} to query the most
* up-to-date state.
* <li>
* <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
* </ul>
*
* @param clientPackageName the package name of the app to control
- * @throws SecurityException if the caller doesn't have {@link
- * Manifest.permission#MEDIA_CONTENT_CONTROL MEDIA_CONTENT_CONTROL} permission.
- * @hide
+ * @return a proxy MediaRouter2 instance if {@code clientPackageName} exists or {@code null}.
*/
- // TODO (b/311711420): Deprecate once #getInstance(Context, String, UserHandle) reaches public
- // SDK.
- @SystemApi
- @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.MEDIA_CONTENT_CONTROL,
+ Manifest.permission.MEDIA_ROUTING_CONTROL
+ })
@Nullable
public static MediaRouter2 getInstance(
@NonNull Context context, @NonNull String clientPackageName) {
@@ -226,9 +226,9 @@ public final class MediaRouter2 {
* {@link RouteDiscoveryPreference.Builder#setPreferredFeatures(List) preferred features}
* when setting a route callback.
* <li>
- * <p>Methods returning non-system {@link RoutingController controllers} always return
- * new instances with the latest data. Do not attempt to compare or store them. Instead,
- * use {@link #getController(String)} or {@link #getControllers()} to query the most
+ * <p>Methods returning non-system {@link RoutingController controllers} always return new
+ * instances with the latest data. Do not attempt to compare or store them. Instead, use
+ * {@link #getController(String)} or {@link #getControllers()} to query the most
* up-to-date state.
* <li>
* <p>Calls to {@link #setOnGetControllerHintsListener} are ignored.
@@ -242,8 +242,8 @@ public final class MediaRouter2 {
* @throws SecurityException if {@code user} does not match {@link Process#myUserHandle()} and
* the caller does not hold {@code Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
* @throws IllegalArgumentException if {@code clientPackageName} does not exist in {@code user}.
+ * @hide
*/
- @FlaggedApi(FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2)
@RequiresPermission(
anyOf = {
Manifest.permission.MEDIA_CONTENT_CONTROL,
@@ -251,9 +251,7 @@ public final class MediaRouter2 {
})
@NonNull
public static MediaRouter2 getInstance(
- @NonNull Context context,
- @NonNull String clientPackageName,
- @NonNull UserHandle user) {
+ @NonNull Context context, @NonNull String clientPackageName, @NonNull UserHandle user) {
return findOrCreateProxyInstanceForCallingUser(context, clientPackageName, user);
}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index d28c26df6749..2202766ef016 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -182,7 +182,7 @@ public final class RoutingSessionInfo implements Parcelable {
mControlHints = src.readBundle();
mIsSystemSession = src.readBoolean();
mTransferReason = src.readInt();
- mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+ mTransferInitiatorUserHandle = UserHandle.readFromParcel(src);
mTransferInitiatorPackageName = src.readString();
}
@@ -417,11 +417,7 @@ public final class RoutingSessionInfo implements Parcelable {
dest.writeBundle(mControlHints);
dest.writeBoolean(mIsSystemSession);
dest.writeInt(mTransferReason);
- if (mTransferInitiatorUserHandle != null) {
- mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
- } else {
- dest.writeParcelable(null, /* flags= */ 0);
- }
+ UserHandle.writeToParcel(mTransferInitiatorUserHandle, dest);
dest.writeString(mTransferInitiatorPackageName);
}
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
new file mode 100644
index 000000000000..c3997e94622d
--- /dev/null
+++ b/media/java/android/media/flags/editing.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.media.editing.flags"
+
+flag {
+ name: "add_media_metrics_editing"
+ namespace: "media_solutions"
+ description: "Add media metrics for transcoding/editing events."
+ bug: "297487694"
+}
diff --git a/media/java/android/media/metrics/EditingEndedEvent.aidl b/media/java/android/media/metrics/EditingEndedEvent.aidl
new file mode 100644
index 000000000000..e099deaa6836
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.metrics;
+
+parcelable EditingEndedEvent;
diff --git a/media/java/android/media/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
new file mode 100644
index 000000000000..72e6db8d987f
--- /dev/null
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.metrics;
+
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.util.Objects;
+
+/** Event for an editing operation having ended. */
+@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+public final class EditingEndedEvent extends Event implements Parcelable {
+
+ // The special value 0 is reserved for the field being unspecified in the proto.
+
+ /** The editing operation was successful. */
+ public static final int FINAL_STATE_SUCCEEDED = 1;
+
+ /** The editing operation was canceled. */
+ public static final int FINAL_STATE_CANCELED = 2;
+
+ /** The editing operation failed due to an error. */
+ public static final int FINAL_STATE_ERROR = 3;
+
+ /** @hide */
+ @IntDef(
+ prefix = {"FINAL_STATE_"},
+ value = {
+ FINAL_STATE_SUCCEEDED,
+ FINAL_STATE_CANCELED,
+ FINAL_STATE_ERROR,
+ })
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ public @interface FinalState {}
+
+ private final @FinalState int mFinalState;
+
+ // The special value 0 is reserved for the field being unspecified in the proto.
+
+ /** Special value representing that no error occurred. */
+ public static final int ERROR_CODE_NONE = 1;
+
+ /** Error code for unexpected runtime errors. */
+ public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 2;
+
+ /** Error code for non-specific errors during input/output. */
+ public static final int ERROR_CODE_IO_UNSPECIFIED = 3;
+
+ /** Error code for network connection failures. */
+ public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 4;
+
+ /** Error code for network timeouts. */
+ public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 5;
+
+ /** Caused by an HTTP server returning an unexpected HTTP response status code. */
+ public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 6;
+
+ /** Caused by a non-existent file. */
+ public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 7;
+
+ /**
+ * Caused by lack of permission to perform an IO operation. For example, lack of permission to
+ * access internet or external storage.
+ */
+ public static final int ERROR_CODE_IO_NO_PERMISSION = 8;
+
+ /** */
+ public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 9;
+
+ /** Caused by reading data out of the data bounds. */
+ public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 10;
+
+ /** Caused by a decoder initialization failure. */
+ public static final int ERROR_CODE_DECODER_INIT_FAILED = 11;
+
+ /** Caused by a failure while trying to decode media samples. */
+ public static final int ERROR_CODE_DECODING_FAILED = 12;
+
+ /** Caused by trying to decode content whose format is not supported. */
+ public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 13;
+
+ /** Caused by an encoder initialization failure. */
+ public static final int ERROR_CODE_ENCODER_INIT_FAILED = 14;
+
+ /** Caused by a failure while trying to encode media samples. */
+ public static final int ERROR_CODE_ENCODING_FAILED = 15;
+
+ /** Caused by trying to encode content whose format is not supported. */
+ public static final int ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED = 16;
+
+ /** Caused by a video frame processing failure. */
+ public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 17;
+
+ /** Caused by an audio processing failure. */
+ public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18;
+
+ /** Caused by a failure while muxing media samples. */
+ public static final int ERROR_CODE_MUXING_FAILED = 19;
+
+ /** @hide */
+ @IntDef(
+ prefix = {"ERROR_CODE_"},
+ value = {
+ ERROR_CODE_NONE,
+ ERROR_CODE_FAILED_RUNTIME_CHECK,
+ ERROR_CODE_IO_UNSPECIFIED,
+ ERROR_CODE_IO_NETWORK_CONNECTION_FAILED,
+ ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT,
+ ERROR_CODE_IO_BAD_HTTP_STATUS,
+ ERROR_CODE_IO_FILE_NOT_FOUND,
+ ERROR_CODE_IO_NO_PERMISSION,
+ ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED,
+ ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE,
+ ERROR_CODE_DECODER_INIT_FAILED,
+ ERROR_CODE_DECODING_FAILED,
+ ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
+ ERROR_CODE_ENCODER_INIT_FAILED,
+ ERROR_CODE_ENCODING_FAILED,
+ ERROR_CODE_ENCODING_FORMAT_UNSUPPORTED,
+ ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED,
+ ERROR_CODE_AUDIO_PROCESSING_FAILED,
+ ERROR_CODE_MUXING_FAILED,
+ })
+ @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ public @interface ErrorCode {}
+
+ private final @ErrorCode int mErrorCode;
+ @SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
+ private final long mTimeSinceCreatedMillis;
+
+ private EditingEndedEvent(
+ @FinalState int finalState,
+ @ErrorCode int errorCode,
+ long timeSinceCreatedMillis,
+ @NonNull Bundle extras) {
+ mFinalState = finalState;
+ mErrorCode = errorCode;
+ mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ mMetricsBundle = extras.deepCopy();
+ }
+
+ /** Returns the state of the editing session when it ended. */
+ @FinalState
+ public int getFinalState() {
+ return mFinalState;
+ }
+
+ /** Returns the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+ @ErrorCode
+ public int getErrorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Gets the elapsed time since creating of the editing session, in milliseconds, or -1 if
+ * unknown.
+ *
+ * @return The elapsed time since creating the editing session, in milliseconds, or -1 if
+ * unknown.
+ * @see LogSessionId
+ * @see EditingSession
+ */
+ @Override
+ @IntRange(from = -1)
+ public long getTimeSinceCreatedMillis() {
+ return mTimeSinceCreatedMillis;
+ }
+
+ /**
+ * Gets metrics-related information that is not supported by dedicated methods.
+ *
+ * <p>It is intended to be used for backwards compatibility by the metrics infrastructure.
+ */
+ @Override
+ @NonNull
+ public Bundle getMetricsBundle() {
+ return mMetricsBundle;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return "PlaybackErrorEvent { "
+ + "finalState = "
+ + mFinalState
+ + ", "
+ + "errorCode = "
+ + mErrorCode
+ + ", "
+ + "timeSinceCreatedMillis = "
+ + mTimeSinceCreatedMillis
+ + " }";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ EditingEndedEvent that = (EditingEndedEvent) o;
+ return mFinalState == that.mFinalState
+ && mErrorCode == that.mErrorCode
+ && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFinalState, mErrorCode, mTimeSinceCreatedMillis);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mFinalState);
+ dest.writeInt(mErrorCode);
+ dest.writeLong(mTimeSinceCreatedMillis);
+ dest.writeBundle(mMetricsBundle);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private EditingEndedEvent(@NonNull Parcel in) {
+ int finalState = in.readInt();
+ int errorCode = in.readInt();
+ long timeSinceCreatedMillis = in.readLong();
+ Bundle metricsBundle = in.readBundle();
+
+ mFinalState = finalState;
+ mErrorCode = errorCode;
+ mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ mMetricsBundle = metricsBundle;
+ }
+
+ public static final @NonNull Creator<EditingEndedEvent> CREATOR =
+ new Creator<>() {
+ @Override
+ public EditingEndedEvent[] newArray(int size) {
+ return new EditingEndedEvent[size];
+ }
+
+ @Override
+ public EditingEndedEvent createFromParcel(@NonNull Parcel in) {
+ return new EditingEndedEvent(in);
+ }
+ };
+
+ /** Builder for {@link EditingEndedEvent} */
+ @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+ public static final class Builder {
+ private final @FinalState int mFinalState;
+ private @ErrorCode int mErrorCode;
+ private long mTimeSinceCreatedMillis;
+ private Bundle mMetricsBundle;
+
+ /**
+ * Creates a new Builder.
+ *
+ * @param finalState The state of the editing session when it ended.
+ */
+ public Builder(@FinalState int finalState) {
+ mFinalState = finalState;
+ mErrorCode = ERROR_CODE_NONE;
+ mTimeSinceCreatedMillis = -1;
+ mMetricsBundle = new Bundle();
+ }
+
+ /**
+ * Sets the elapsed time since creating the editing session, in milliseconds.
+ *
+ * @param timeSinceCreatedMillis The elapsed time since creating the editing session, in
+ * milliseconds, or -1 if the value is unknown.
+ * @see #getTimeSinceCreatedMillis()
+ */
+ public @NonNull Builder setTimeSinceCreatedMillis(
+ @IntRange(from = -1) long timeSinceCreatedMillis) {
+ mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+ return this;
+ }
+
+ /** Sets the error code for a {@linkplain #FINAL_STATE_ERROR failed} editing session. */
+ public @NonNull Builder setErrorCode(@ErrorCode int value) {
+ mErrorCode = value;
+ return this;
+ }
+
+ /**
+ * Sets metrics-related information that is not supported by dedicated methods.
+ *
+ * <p>Used for backwards compatibility by the metrics infrastructure.
+ */
+ public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
+ mMetricsBundle = metricsBundle;
+ return this;
+ }
+
+ /** Builds an instance. */
+ public @NonNull EditingEndedEvent build() {
+ return new EditingEndedEvent(
+ mFinalState, mErrorCode, mTimeSinceCreatedMillis, mMetricsBundle);
+ }
+ }
+}
diff --git a/media/java/android/media/metrics/EditingSession.java b/media/java/android/media/metrics/EditingSession.java
index 2ddf623b1ed3..964e12cfcc05 100644
--- a/media/java/android/media/metrics/EditingSession.java
+++ b/media/java/android/media/metrics/EditingSession.java
@@ -16,6 +16,9 @@
package android.media.metrics;
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -24,7 +27,8 @@ import com.android.internal.util.AnnotationValidations;
import java.util.Objects;
/**
- * An instances of this class represents a session of media editing.
+ * Represents a session of media editing, for example, transcoding between formats, transmuxing or
+ * applying trimming or audio/video effects to a stream.
*/
public final class EditingSession implements AutoCloseable {
private final @NonNull String mId;
@@ -40,6 +44,13 @@ public final class EditingSession implements AutoCloseable {
mLogSessionId = new LogSessionId(mId);
}
+ /** Reports that an editing operation ended. */
+ @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+ public void reportEditingEndedEvent(@NonNull EditingEndedEvent editingEndedEvent) {
+ mManager.reportEditingEndedEvent(mId, editingEndedEvent);
+ }
+
+ /** Returns the identifier for logging this session. */
public @NonNull LogSessionId getSessionId() {
return mLogSessionId;
}
diff --git a/media/java/android/media/metrics/IMediaMetricsManager.aidl b/media/java/android/media/metrics/IMediaMetricsManager.aidl
index 51b1cc27c8a7..e07ca67296b3 100644
--- a/media/java/android/media/metrics/IMediaMetricsManager.aidl
+++ b/media/java/android/media/metrics/IMediaMetricsManager.aidl
@@ -16,6 +16,7 @@
package android.media.metrics;
+import android.media.metrics.EditingEndedEvent;
import android.media.metrics.NetworkEvent;
import android.media.metrics.PlaybackErrorEvent;
import android.media.metrics.PlaybackMetrics;
@@ -24,7 +25,7 @@ import android.media.metrics.TrackChangeEvent;
import android.os.PersistableBundle;
/**
- * Interface to the playback manager service.
+ * Interface to the media metrics manager service.
* @hide
*/
interface IMediaMetricsManager {
@@ -37,6 +38,8 @@ interface IMediaMetricsManager {
void reportPlaybackStateEvent(in String sessionId, in PlaybackStateEvent event, int userId);
void reportTrackChangeEvent(in String sessionId, in TrackChangeEvent event, int userId);
+ void reportEditingEndedEvent(in String sessionId, in EditingEndedEvent event, int userId);
+
String getTranscodingSessionId(int userId);
String getEditingSessionId(int userId);
String getBundleSessionId(int userId);
diff --git a/media/java/android/media/metrics/MediaMetricsManager.java b/media/java/android/media/metrics/MediaMetricsManager.java
index 0898874c2f65..622b0c158b8a 100644
--- a/media/java/android/media/metrics/MediaMetricsManager.java
+++ b/media/java/android/media/metrics/MediaMetricsManager.java
@@ -193,4 +193,18 @@ public final class MediaMetricsManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Reports the event of an editing session ending.
+ *
+ * @hide
+ */
+ public void reportEditingEndedEvent(
+ @NonNull String sessionId, EditingEndedEvent editingEndedEvent) {
+ try {
+ mService.reportEditingEndedEvent(sessionId, editingEndedEvent, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index e3dba03d6093..7b5853169923 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -61,7 +61,8 @@ oneway interface ITvInteractiveAppClient {
void onSetTvRecordingInfo(in String recordingId, in TvRecordingInfo recordingInfo, int seq);
void onRequestTvRecordingInfo(in String recordingId, int seq);
void onRequestTvRecordingInfoList(in int type, int seq);
- void onRequestSigning(
- in String id, in String algorithm, in String alias, in byte[] data, int seq);
+ void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data,
+ int seq);
+ void onRequestCertificate(in String host, int port, int seq);
void onAdRequest(in AdRequest request, int Seq);
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 0f58b29247bb..1b9450b240ac 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -58,6 +58,8 @@ interface ITvInteractiveAppManager {
void sendAvailableSpeeds(in IBinder sessionToken, in float[] speeds, int userId);
void sendSigningResult(in IBinder sessionToken, in String signingId, in byte[] result,
int userId);
+ void sendCertificate(in IBinder sessionToken, in String host, int port,
+ in Bundle certBundle, int userId);
void sendTvRecordingInfo(in IBinder sessionToken, in TvRecordingInfo recordingInfo, int userId);
void sendTvRecordingInfoList(in IBinder sessionToken,
in List<TvRecordingInfo> recordingInfoList, int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 06808c9ff915..3969315ab655 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -49,6 +49,7 @@ oneway interface ITvInteractiveAppSession {
void sendTimeShiftMode(int mode);
void sendAvailableSpeeds(in float[] speeds);
void sendSigningResult(in String signingId, in byte[] result);
+ void sendCertificate(in String host, int port, in Bundle certBundle);
void sendTvRecordingInfo(in TvRecordingInfo recordingInfo);
void sendTvRecordingInfoList(in List<TvRecordingInfo> recordingInfoList);
void notifyError(in String errMsg, in Bundle params);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 416b8f12d5ea..cb89181fd714 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -61,5 +61,6 @@ oneway interface ITvInteractiveAppSessionCallback {
void onRequestTvRecordingInfo(in String recordingId);
void onRequestTvRecordingInfoList(in int type);
void onRequestSigning(in String id, in String algorithm, in String alias, in byte[] data);
+ void onRequestCertificate(in String host, int port);
void onAdRequest(in AdRequest request);
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 77730aa46d0a..ec6c2bfab576 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -104,6 +104,7 @@ public class ITvInteractiveAppSessionWrapper
private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
private static final int DO_NOTIFY_VIDEO_FREEZE_UPDATED = 49;
+ private static final int DO_SEND_CERTIFICATE = 50;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -369,6 +370,13 @@ public class ITvInteractiveAppSessionWrapper
mSessionImpl.notifyVideoFreezeUpdated((Boolean) msg.obj);
break;
}
+ case DO_SEND_CERTIFICATE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.sendCertificate((String) args.arg1, (Integer) args.arg2,
+ (Bundle) args.arg3);
+ args.recycle();
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -483,6 +491,12 @@ public class ITvInteractiveAppSessionWrapper
}
@Override
+ public void sendCertificate(@NonNull String host, int port, @NonNull Bundle certBundle) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageOOO(DO_SEND_CERTIFICATE, host, port, certBundle));
+ }
+
+ @Override
public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
mCaller.executeOrSendMessage(
mCaller.obtainMessageOO(DO_NOTIFY_ERROR, errMsg, params));
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 8a340f6862bb..011744f94edb 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -34,6 +34,7 @@ import android.media.tv.TvInputManager;
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -656,6 +657,18 @@ public final class TvInteractiveAppManager {
}
@Override
+ public void onRequestCertificate(String host, int port, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestCertificate(host, port);
+ }
+ }
+
+ @Override
public void onSessionStateChanged(int state, int err, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1328,6 +1341,19 @@ public final class TvInteractiveAppManager {
}
}
+ void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendCertificate(mToken, host, port, SslCertificate.saveState(cert),
+ mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -2232,6 +2258,15 @@ public final class TvInteractiveAppManager {
});
}
+ void postRequestCertificate(String host, int port) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestCertificate(mSession, host, port);
+ }
+ });
+ }
+
void postRequestTvRecordingInfo(String recordingId) {
mHandler.post(new Runnable() {
@Override
@@ -2574,6 +2609,17 @@ public final class TvInteractiveAppManager {
}
/**
+ * This is called when the service requests a SSL certificate for client validation.
+ *
+ * @param session A {@link TvInteractiveAppService.Session} associated with this callback.
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @hide
+ */
+ public void onRequestCertificate(Session session, String host, int port) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#notifySessionStateChanged} is
* called.
*
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 5247a0ebe6e0..054b272d820f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -46,6 +46,7 @@ import android.media.tv.TvTrackInfo;
import android.media.tv.TvView;
import android.media.tv.interactive.TvInteractiveAppView.TvInteractiveAppCallback;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
@@ -734,6 +735,17 @@ public abstract class TvInteractiveAppService extends Service {
}
/**
+ * Receives the requested Certificate
+ *
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @param cert the SSL certificate received.
+ * @hide
+ */
+ public void onCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+ }
+
+ /**
* Called when the application sends information of an error.
*
* @param errMsg the message of the error.
@@ -1633,6 +1645,32 @@ public abstract class TvInteractiveAppService extends Service {
}
/**
+ * Requests a SSL certificate for client validation.
+ *
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @hide
+ */
+ public void requestCertificate(@NonNull String host, int port) {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestCertificate");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestCertificate(host, port);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestCertificate", e);
+ }
+ }
+ });
+ }
+
+ /**
* Sends an advertisement request to be processed by the related TV input.
*
* @param request The advertisement request
@@ -1725,6 +1763,11 @@ public abstract class TvInteractiveAppService extends Service {
onSigningResult(signingId, result);
}
+ void sendCertificate(String host, int port, Bundle certBundle) {
+ SslCertificate cert = SslCertificate.restoreState(certBundle);
+ onCertificate(host, port, cert);
+ }
+
void notifyError(String errMsg, Bundle params) {
onError(errMsg, params);
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index 5bb61c261ae2..3b295742c244 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -34,6 +34,7 @@ import android.media.tv.interactive.TvInteractiveAppManager.Session;
import android.media.tv.interactive.TvInteractiveAppManager.Session.FinishedInputEventCallback;
import android.media.tv.interactive.TvInteractiveAppManager.SessionCallback;
import android.net.Uri;
+import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
@@ -756,6 +757,22 @@ public class TvInteractiveAppView extends ViewGroup {
}
/**
+ * Send the requested SSL certificate to the TV Interactive App
+ * @param host the host name of the SSL authentication server.
+ * @param port the port of the SSL authentication server. E.g., 443
+ * @param cert the SSL certificate requested
+ * @hide
+ */
+ public void sendCertificate(@NonNull String host, int port, @NonNull SslCertificate cert) {
+ if (DEBUG) {
+ Log.d(TAG, "sendCertificate");
+ }
+ if (mSession != null) {
+ mSession.sendCertificate(host, port, cert);
+ }
+ }
+
+ /**
* Notifies the corresponding {@link TvInteractiveAppService} when there is an error.
*
* @param errMsg the message of the error.
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index 52060f1e6209..66fec1c528e7 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -35,10 +35,9 @@ static constexpr int32_t kMaxStreams = 32;
// In R, we change this to true, as it is the correct way per SoundPool documentation.
static constexpr bool kStealActiveStream_OldestFirst = true;
-// kPlayOnCallingThread = true prior to R.
// Changing to false means calls to play() are almost instantaneous instead of taking around
// ~10ms to launch the AudioTrack. It is perhaps 100x faster.
-static constexpr bool kPlayOnCallingThread = true;
+static constexpr bool kPlayOnCallingThread = false;
// Amount of time for a StreamManager thread to wait before closing.
static constexpr int64_t kWaitTimeBeforeCloseNs = 9 * NANOS_PER_SECOND;
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
index adbab4b0f9d9..340b49bc6d6c 100644
--- a/media/jni/soundpool/StreamManager.h
+++ b/media/jni/soundpool/StreamManager.h
@@ -48,7 +48,7 @@ class JavaThread {
public:
JavaThread(std::function<void()> f, const char *name)
: mF{std::move(f)} {
- createThreadEtc(staticFunction, this, name);
+ createThreadEtc(staticFunction, this, name, ANDROID_PRIORITY_AUDIO);
}
JavaThread(JavaThread &&) = delete; // uses "this" ptr, not moveable.
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index 25040a942061..e872a58c96cf 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -86,7 +86,7 @@ public:
}
// Retrieves the associated object, returns nullValue T if not available.
- T get(JNIEnv *env, jobject thiz) {
+ T get(JNIEnv *env, jobject thiz) const {
std::lock_guard lg(mLock);
// NOLINTNEXTLINE(performance-no-int-to-ptr)
auto ptr = reinterpret_cast<T*>(env->GetLongField(thiz, mFieldId));
@@ -167,8 +167,10 @@ private:
// is possible by checking if the WeakGlobalRef is null equivalent.
auto& getSoundPoolManager() {
- static ObjectManager<std::shared_ptr<SoundPool>> soundPoolManager(fields.mNativeContext);
- return soundPoolManager;
+ // never-delete singleton
+ static auto soundPoolManager =
+ new ObjectManager<std::shared_ptr<SoundPool>>(fields.mNativeContext);
+ return *soundPoolManager;
}
inline auto getSoundPool(JNIEnv *env, jobject thiz) {
@@ -274,8 +276,9 @@ static_assert(std::is_same_v<JWeakValue*, jweak>);
auto& getSoundPoolJavaRefManager() {
// Note this can store shared_ptrs to either jweak and jobject,
// as the underlying type is identical.
- static ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>> concurrentHashMap;
- return concurrentHashMap;
+ static auto concurrentHashMap =
+ new ConcurrentHashMap<SoundPool *, std::shared_ptr<JWeakValue>>();
+ return *concurrentHashMap;
}
// make_shared_globalref_from_localref() creates a sharable Java global
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
index 4f6ede508f7a..46256ba7cccb 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecControllerTest.java
@@ -126,7 +126,7 @@ public class LoudnessCodecControllerTest {
try {
mLcc.addMediaCodec(mediaCodec);
- mLcc.release(); // stops updats
+ mLcc.close(); // stops updates
verify(mAudioService).stopLoudnessCodecUpdates(eq(mSessionId));
} finally {
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 10c570b30d7a..8ea46329af58 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -72,6 +72,9 @@ cc_library_shared {
],
},
},
+ stubs: {
+ symbol_file: "libjnigraphics.map.txt",
+ },
}
// The headers module is in frameworks/native/Android.bp.
@@ -93,15 +96,18 @@ cc_defaults {
],
static_libs: ["libarect"],
fuzz_config: {
- cc: ["dichenzhang@google.com","scroggo@google.com"],
+ cc: [
+ "dichenzhang@google.com",
+ "scroggo@google.com",
+ ],
asan_options: [
"detect_odr_violation=1",
],
hwasan_options: [
- // Image decoders may attempt to allocate a large amount of memory
- // (especially if the encoded image is large). This doesn't
- // necessarily mean there is a bug. Set allocator_may_return_null=1
- // for hwasan so the fuzzer can continue running.
+ // Image decoders may attempt to allocate a large amount of memory
+ // (especially if the encoded image is large). This doesn't
+ // necessarily mean there is a bug. Set allocator_may_return_null=1
+ // for hwasan so the fuzzer can continue running.
"allocator_may_return_null = 1",
],
},
diff --git a/packages/CrashRecovery/aconfig/flags.aconfig b/packages/CrashRecovery/aconfig/flags.aconfig
new file mode 100644
index 000000000000..563626634068
--- /dev/null
+++ b/packages/CrashRecovery/aconfig/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.crashrecovery.flags"
+
+flag {
+ name: "recoverability_detection"
+ namespace: "package_watchdog"
+ description: "Feature flag for recoverability detection"
+ bug: "310236690"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
index 5becc86927d2..f13402c7206d 100644
--- a/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
+++ b/packages/CredentialManager/res/drawable/fill_dialog_dynamic_list_item_one.xml
@@ -23,7 +23,7 @@
android:shape="rectangle"
android:top="1dp">
<shape>
- <corners android:radius="16dp" />
+ <corners android:radius="4dp" />
<solid android:color="@color/dropdown_container" />
</shape>
</item>
diff --git a/packages/CredentialManager/res/drawable/more_options_list_item.xml b/packages/CredentialManager/res/drawable/more_options_list_item.xml
new file mode 100644
index 000000000000..d7b509ee48fd
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_options_list_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools" tools:ignore="NewApi"
+ android:color="@android:color/transparent">
+ <item
+ android:bottom="1dp"
+ android:shape="rectangle"
+ android:top="1dp">
+ <shape>
+ <corners android:bottomLeftRadius="4dp"
+ android:bottomRightRadius="4dp"/>
+ <solid android:color="@color/sign_in_options_container" />
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
new file mode 100644
index 000000000000..929756cdf9cc
--- /dev/null
+++ b/packages/CredentialManager/res/layout/credman_dropdown_bottom_sheet.xml
@@ -0,0 +1,42 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
+ android:elevation="3dp">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:contentDescription="@string/provider_icon_content_description"
+ android:background="@null"/>
+ <TextView
+ android:id="@android:id/text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
+ style="@style/autofill.TextTitle"/>
+
+</RelativeLayout>
diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
index cb6c6b473244..1fe5e0ed41f9 100644
--- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
+++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml
@@ -17,22 +17,25 @@
android:id="@android:id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxWidth="@dimen/autofill_dropdown_layout_width"
+ android:layout_marginEnd="@dimen/dropdown_layout_horizontal_margin"
android:elevation="3dp">
<ImageView
android:id="@android:id/icon1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:contentDescription="@string/provider_icon_content_description"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:background="@null"/>
<TextView
android:id="@android:id/text1"
- android:layout_width="@dimen/autofill_dropdown_text_width"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextTitle"/>
<TextView
android:id="@android:id/text2"
@@ -40,6 +43,8 @@
android:layout_height="wrap_content"
android:layout_below="@android:id/text1"
android:layout_toEndOf="@android:id/icon1"
+ android:minWidth="@dimen/autofill_dropdown_textview_min_width"
+ android:maxWidth="@dimen/autofill_dropdown_textview_max_width"
style="@style/autofill.TextSubtitle"/>
</RelativeLayout>
diff --git a/packages/CredentialManager/res/values/colors.xml b/packages/CredentialManager/res/values/colors.xml
index dcb7ef9c3ed8..7cb1d01972b7 100644
--- a/packages/CredentialManager/res/values/colors.xml
+++ b/packages/CredentialManager/res/values/colors.xml
@@ -20,4 +20,6 @@
<color name="text_primary">#1A1B20</color>
<color name="text_secondary">#44474F</color>
<color name="dropdown_container">#F3F3FA</color>
+ <color name="sign_in_options_container">#DADADA</color>
+ <color name="sign_in_options_icon_color">#1B1B1B</color>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/dimens.xml b/packages/CredentialManager/res/values/dimens.xml
index 2a4719d027e2..3a8c78f6d854 100644
--- a/packages/CredentialManager/res/values/dimens.xml
+++ b/packages/CredentialManager/res/values/dimens.xml
@@ -18,11 +18,13 @@
<resources>
<dimen name="autofill_view_top_padding">12dp</dimen>
- <dimen name="autofill_view_right_padding">24dp</dimen>
+ <dimen name="autofill_view_right_padding">12dp</dimen>
<dimen name="autofill_view_bottom_padding">12dp</dimen>
<dimen name="autofill_view_left_padding">16dp</dimen>
<dimen name="autofill_view_icon_to_text_padding">10dp</dimen>
<dimen name="autofill_icon_size">24dp</dimen>
- <dimen name="autofill_dropdown_layout_width">296dp</dimen>
- <dimen name="autofill_dropdown_text_width">240dp</dimen>
+ <dimen name="autofill_dropdown_textview_min_width">112dp</dimen>
+ <dimen name="autofill_dropdown_textview_max_width">230dp</dimen>
+ <dimen name="dropdown_layout_horizontal_margin">24dp</dimen>
+ <integer name="autofill_max_visible_datasets">3</integer>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 605e77bef34e..f98164b8788c 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -168,4 +168,9 @@
<string name="get_dialog_option_headline_use_a_different_device">Use a different device</string>
<!-- Text shown on a snackbar when the app cancelled the UI. [CHAR LIMIT=120] -->
<string name="request_cancelled_by">Request cancelled by <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+
+ <!-- Strings for dropdown presentation. -->
+ <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] -->
+ <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string>
+ <string name="provider_icon_content_description">Credential provider icon</string>
</resources> \ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 03ac605222ba..985f3228f402 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -30,6 +30,7 @@ import android.credentials.ui.GetCredentialProviderData
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
+import android.provider.Settings
import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
@@ -48,7 +49,9 @@ import android.view.autofill.IAutoFillManagerClient
import android.view.autofill.AutofillId
import android.widget.inline.InlinePresentationSpec
import android.credentials.CredentialManager
+import android.widget.RemoteViews
import androidx.autofill.inline.v1.InlineSuggestionUi
+import androidx.core.content.ContextCompat
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
import androidx.credentials.provider.PublicKeyCredentialEntry
@@ -115,7 +118,7 @@ class CredentialAutofillService : AutofillService() {
}
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId,
- requestId)
+ requestId)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
@@ -307,10 +310,14 @@ class CredentialAutofillService : AutofillService() {
val inlineMaxSuggestedCount = inlineSuggestionsRequest?.maxSuggestionCount ?: 0
val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs
val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0
- var maxItemCount = totalEntryCount
- if (inlineMaxSuggestedCount > 0) {
- maxItemCount = maxItemCount.coerceAtMost(inlineMaxSuggestedCount)
- }
+ val maxDropdownDisplayLimit = this.resources.getInteger(
+ com.android.credentialmanager.R.integer.autofill_max_visible_datasets)
+ var maxInlineItemCount = totalEntryCount
+ maxInlineItemCount = maxInlineItemCount.coerceAtMost(inlineMaxSuggestedCount)
+ val lastDropdownDatasetIndex = Settings.Global.getInt(this.contentResolver,
+ Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
+ (maxDropdownDisplayLimit - 1).coerceAtMost(totalEntryCount - 1))
+
var i = 0
var datasetAdded = false
@@ -333,13 +340,8 @@ class CredentialAutofillService : AutofillService() {
Log.e(TAG, "PendingIntent was missing from the entry.")
return@usernameLoop
}
- if (inlinePresentationSpecs == null) {
- Log.i(TAG, "Inline presentation spec is null, " +
- "building dropdown presentation only")
- }
- if (i >= maxItemCount) {
- Log.e(TAG, "Skipping because reached the max item count.")
- return@usernameLoop
+ if (i >= maxInlineItemCount && i >= lastDropdownDatasetIndex) {
+ return@usernameLoop;
}
val icon: Icon = if (primaryEntry.icon == null) {
// The empty entry icon has non-null icon reference but null drawable reference.
@@ -351,38 +353,26 @@ class CredentialAutofillService : AutofillService() {
}
// Create inline presentation
var inlinePresentation: InlinePresentation? = null
- var spec: InlinePresentationSpec?
- if (inlinePresentationSpecs != null) {
- if (i < inlinePresentationSpecsCount) {
- spec = inlinePresentationSpecs[i]
+ if (inlinePresentationSpecs != null && i < maxInlineItemCount) {
+ val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) {
+ inlinePresentationSpecs[i]
} else {
- spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+ inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
}
- val displayName: String = if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && primaryEntry.displayName != null) {
- primaryEntry.displayName!!
- } else {
- primaryEntry.userName
- }
- val sliceBuilder = InlineSuggestionUi
- .newContentBuilder(pendingIntent)
- .setTitle(displayName)
- sliceBuilder.setStartIcon(icon)
- if (primaryEntry.credentialType ==
- CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[displayName]
- == true) {
- sliceBuilder.setSubtitle(primaryEntry.userName)
- }
- inlinePresentation = InlinePresentation(
- sliceBuilder.build().slice, spec, /* pinned= */ false)
+ inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon,
+ spec!!, duplicateDisplayNamesForPasskeys)
+ }
+ var dropdownPresentation: RemoteViews? = null
+ if (i < lastDropdownDatasetIndex) {
+ dropdownPresentation = RemoteViewsFactory
+ .createDropdownPresentation(this, icon, primaryEntry)
}
- val dropdownPresentation = RemoteViewsFactory.createDropdownPresentation(
- this, icon, primaryEntry)
- i++
val dataSetBuilder = Dataset.Builder()
val presentationBuilder = Presentations.Builder()
- .setMenuPresentation(dropdownPresentation)
+ if (dropdownPresentation != null) {
+ presentationBuilder.setMenuPresentation(dropdownPresentation)
+ }
if (inlinePresentation != null) {
presentationBuilder.setInlinePresentation(inlinePresentation)
}
@@ -398,6 +388,12 @@ class CredentialAutofillService : AutofillService() {
.setAuthenticationExtras(fillInIntent.extras)
.build())
datasetAdded = true
+ i++
+
+ if (i == lastDropdownDatasetIndex && bottomSheetPendingIntent != null) {
+ addDropdownMoreOptionsPresentation(bottomSheetPendingIntent, autofillId,
+ fillResponseBuilder)
+ }
}
val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
inlinePresentationSpecsCount)
@@ -408,6 +404,49 @@ class CredentialAutofillService : AutofillService() {
return datasetAdded
}
+ private fun createInlinePresentation(primaryEntry: CredentialEntryInfo,
+ pendingIntent: PendingIntent,
+ icon: Icon,
+ spec: InlinePresentationSpec,
+ duplicateDisplayNameForPasskeys: MutableMap<String, Boolean>):
+ InlinePresentation {
+ val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY
+ && primaryEntry.displayName != null) {
+ primaryEntry.displayName!!
+ } else {
+ primaryEntry.userName
+ }
+ val sliceBuilder = InlineSuggestionUi
+ .newContentBuilder(pendingIntent)
+ .setTitle(displayName)
+ sliceBuilder.setStartIcon(icon)
+ if (primaryEntry.credentialType ==
+ CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
+ sliceBuilder.setSubtitle(primaryEntry.userName)
+ }
+ return InlinePresentation(
+ sliceBuilder.build().slice, spec, /* pinned= */ false)
+ }
+
+ private fun addDropdownMoreOptionsPresentation(
+ bottomSheetPendingIntent: PendingIntent,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder) {
+ val presentationBuilder = Presentations.Builder()
+ .setMenuPresentation(RemoteViewsFactory.createMoreSignInOptionsPresentation(this))
+
+ fillResponseBuilder.addDataset(
+ Dataset.Builder()
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentationBuilder.build())
+ .build())
+ .setAuthentication(bottomSheetPendingIntent.intentSender)
+ .build()
+ )
+ }
+
private fun getLastInlinePresentationSpec(
inlinePresentationSpecs: List<InlinePresentationSpec>?,
inlinePresentationSpecsCount: Int
@@ -534,9 +573,9 @@ class CredentialAutofillService : AutofillService() {
}
private fun getCredManRequest(
- structure: AssistStructure,
- sessionId: Int,
- requestId: Int
+ structure: AssistStructure,
+ sessionId: Int,
+ requestId: Int
): GetCredentialRequest? {
val credentialOptions: MutableList<CredentialOption> = mutableListOf()
traverseStructure(structure, credentialOptions)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
index e039dead043e..68f1c861d51b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt
@@ -44,7 +44,7 @@ class RemoteViewsFactory {
if (credentialEntryInfo.credentialType == CredentialType.UNKNOWN) {
return remoteViews
}
- setRemoteViewsPaddings(remoteViews, context)
+ setRemoteViewsPaddings(remoteViews, context, /* primaryTextBottomPadding=*/0)
if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) {
val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName
remoteViews.setTextViewText(android.R.id.text1, displayName)
@@ -81,8 +81,46 @@ class RemoteViewsFactory {
return remoteViews
}
+ fun createMoreSignInOptionsPresentation(context: Context): RemoteViews {
+ var layoutId: Int = com.android.credentialmanager.R.layout
+ .credman_dropdown_bottom_sheet
+ val remoteViews = RemoteViews(context.packageName, layoutId)
+ setRemoteViewsPaddings(remoteViews, context)
+ remoteViews.setTextViewText(android.R.id.text1, ContextCompat.getString(context,
+ com.android.credentialmanager
+ .R.string.dropdown_presentation_more_sign_in_options_text))
+
+ val textColorPrimary = ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.text_primary)
+ remoteViews.setTextColor(android.R.id.text1, textColorPrimary)
+ val icon = Icon.createWithResource(context, com
+ .android.credentialmanager.R.drawable.more_horiz_24px)
+ icon.setTint(ContextCompat.getColor(context,
+ com.android.credentialmanager.R.color.sign_in_options_icon_color))
+ remoteViews.setImageViewIcon(android.R.id.icon1, icon)
+ remoteViews.setBoolean(
+ android.R.id.icon1, setAdjustViewBoundsMethodName, true);
+ remoteViews.setInt(
+ android.R.id.icon1,
+ setMaxHeightMethodName,
+ context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_icon_size));
+ val drawableId =
+ com.android.credentialmanager.R.drawable.more_options_list_item
+ remoteViews.setInt(
+ android.R.id.content, setBackgroundResourceMethodName, drawableId);
+ return remoteViews
+ }
+
private fun setRemoteViewsPaddings(
remoteViews: RemoteViews, context: Context) {
+ val bottomPadding = context.resources.getDimensionPixelSize(
+ com.android.credentialmanager.R.dimen.autofill_view_bottom_padding)
+ setRemoteViewsPaddings(remoteViews, context, bottomPadding)
+ }
+
+ private fun setRemoteViewsPaddings(
+ remoteViews: RemoteViews, context: Context, primaryTextBottomPadding: Int) {
val leftPadding = context.resources.getDimensionPixelSize(
com.android.credentialmanager.R.dimen.autofill_view_left_padding)
val iconToTextPadding = context.resources.getDimensionPixelSize(
@@ -104,7 +142,7 @@ class RemoteViewsFactory {
iconToTextPadding,
/* top=*/topPadding,
/* right=*/rightPadding,
- /* bottom=*/0)
+ primaryTextBottomPadding)
remoteViews.setViewPadding(
android.R.id.text2,
iconToTextPadding,
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c2cb75709b45..bd56aae1cb17 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -16,6 +16,7 @@ android_library {
static_libs: [
"androidx.localbroadcastmanager_localbroadcastmanager",
"androidx.room_room-runtime",
+ "androidx.sqlite_sqlite",
"zxing-core",
"guava",
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
index e099f1124bf1..0a424bc4230a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
@@ -37,10 +37,11 @@ private const val TAG = "BroadcastReceiverFlow"
fun Context.broadcastReceiverFlow(intentFilter: IntentFilter): Flow<Intent> = callbackFlow {
val broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
+ Log.d(TAG, "onReceive: $intent")
trySend(intent)
}
}
- registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
+ registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS)
awaitClose { unregisterReceiver(broadcastReceiver) }
}.catch { e ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 81a8b324f70f..cea3d13e27ab 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -37,6 +37,7 @@ fun rememberAppRepository(): AppRepository = rememberContext(::AppRepositoryImpl
interface AppRepository {
fun loadLabel(app: ApplicationInfo): String
+ @Suppress("ABSTRACT_COMPOSABLE_DEFAULT_PARAMETER_VALUE")
@Composable
fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
val context = LocalContext.current
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
index eef5225aef42..772f925c0a77 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
@@ -43,7 +43,7 @@ class BroadcastReceiverFlowTest {
private val context = mock<Context> {
on {
- registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_NOT_EXPORTED))
+ registerReceiver(any(), eq(INTENT_FILTER), eq(Context.RECEIVER_VISIBLE_TO_INSTANT_APPS))
} doAnswer {
registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
null
diff --git a/packages/SettingsLib/res/drawable/ic_external_display.xml b/packages/SettingsLib/res/drawable/ic_external_display.xml
new file mode 100644
index 000000000000..de50de8c07c8
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_external_display.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="25dp"
+ android:viewportWidth="24"
+ android:viewportHeight="25">
+ <group>
+ <clip-path
+ android:pathData="M0,0.307h24v24h-24z"/>
+ <path
+ android:pathData="M8,21.307V19.307H10V17.307H4C3.45,17.307 2.975,17.115 2.575,16.732C2.192,16.332 2,15.857 2,15.307V5.307C2,4.757 2.192,4.29 2.575,3.907C2.975,3.507 3.45,3.307 4,3.307H20C20.55,3.307 21.017,3.507 21.4,3.907C21.8,4.29 22,4.757 22,5.307V15.307C22,15.857 21.8,16.332 21.4,16.732C21.017,17.115 20.55,17.307 20,17.307H14V19.307H16V21.307H8ZM4,15.307H20V5.307H4V15.307ZM4,15.307V5.307V15.307Z"
+ android:fillColor="#E5E3D6"/>
+ </group>
+</vector>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index cf4d6be9a042..0613676113f5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -99,6 +99,8 @@ public class HapClientProfile implements LocalBluetoothProfile {
device.refresh();
}
+ // Check current list of CachedDevices to see if any are hearing aid devices.
+ mDeviceManager.updateHearingAidsDevices();
mIsProfileReady = true;
mProfileManager.callServiceConnectedListeners();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index 3a15b7108e3f..9fd174d4586c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -15,8 +15,8 @@
*/
package com.android.settingslib.bluetooth;
-import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.ScanFilter;
@@ -68,14 +68,9 @@ public class HearingAidDeviceManager {
void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice,
List<ScanFilter> leScanFilters) {
- long hiSyncId = getHiSyncId(newDevice.getDevice());
- if (isValidHiSyncId(hiSyncId)) {
- // Once hiSyncId is valid, assign hearing aid info
- final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
- .setAshaDeviceSide(getDeviceSide(newDevice.getDevice()))
- .setAshaDeviceMode(getDeviceMode(newDevice.getDevice()))
- .setHiSyncId(hiSyncId);
- newDevice.setHearingAidInfo(infoBuilder.build());
+ HearingAidInfo info = generateHearingAidInfo(newDevice);
+ if (info != null) {
+ newDevice.setHearingAidInfo(info);
} else if (leScanFilters != null && !newDevice.isHearingAidDevice()) {
// If the device is added with hearing aid scan filter during pairing, set an empty
// hearing aid info to indicate it's a hearing aid device. The info will be updated
@@ -94,38 +89,6 @@ public class HearingAidDeviceManager {
}
}
- private long getHiSyncId(BluetoothDevice device) {
- final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
- if (profileProxy == null) {
- return BluetoothHearingAid.HI_SYNC_ID_INVALID;
- }
-
- return profileProxy.getHiSyncId(device);
- }
-
- private int getDeviceSide(BluetoothDevice device) {
- final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
- if (profileProxy == null) {
- Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device side");
- return HearingAidProfile.DeviceSide.SIDE_INVALID;
- }
-
- return profileProxy.getDeviceSide(device);
- }
-
- private int getDeviceMode(BluetoothDevice device) {
- final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
- final HearingAidProfile profileProxy = profileManager.getHearingAidProfile();
- if (profileProxy == null) {
- Log.w(TAG, "HearingAidProfile is not supported and not ready to fetch device mode");
- return HearingAidProfile.DeviceMode.MODE_INVALID;
- }
-
- return profileProxy.getDeviceMode(device);
- }
-
boolean setSubDeviceIfNeeded(CachedBluetoothDevice newDevice) {
final long hiSyncId = newDevice.getHiSyncId();
if (isValidHiSyncId(hiSyncId)) {
@@ -157,21 +120,17 @@ public class HearingAidDeviceManager {
// To collect all HearingAid devices and call #onHiSyncIdChanged to group device by HiSyncId
void updateHearingAidsDevices() {
- final Set<Long> newSyncIdSet = new HashSet<Long>();
+ final Set<Long> newSyncIdSet = new HashSet<>();
for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
// Do nothing if HiSyncId has been assigned
- if (!isValidHiSyncId(cachedDevice.getHiSyncId())) {
- final long newHiSyncId = getHiSyncId(cachedDevice.getDevice());
- // Do nothing if there is no HiSyncId on Bluetooth device
- if (isValidHiSyncId(newHiSyncId)) {
- // Once hiSyncId is valid, assign hearing aid info
- final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
- .setAshaDeviceSide(getDeviceSide(cachedDevice.getDevice()))
- .setAshaDeviceMode(getDeviceMode(cachedDevice.getDevice()))
- .setHiSyncId(newHiSyncId);
- cachedDevice.setHearingAidInfo(infoBuilder.build());
-
- newSyncIdSet.add(newHiSyncId);
+ if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
+ continue;
+ }
+ HearingAidInfo info = generateHearingAidInfo(cachedDevice);
+ if (info != null) {
+ cachedDevice.setHearingAidInfo(info);
+ if (isValidHiSyncId(info.getHiSyncId())) {
+ newSyncIdSet.add(info.getHiSyncId());
}
}
}
@@ -378,6 +337,54 @@ public class HearingAidDeviceManager {
return null;
}
+ private boolean isLeAudioHearingAid(CachedBluetoothDevice cachedDevice) {
+ List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
+ boolean supportLeAudio = profiles.stream().anyMatch(p -> p instanceof LeAudioProfile);
+ boolean supportHapClient = profiles.stream().anyMatch(p -> p instanceof HapClientProfile);
+ return supportLeAudio && supportHapClient;
+ }
+
+ private boolean isAshaHearingAid(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice.getProfiles().stream().anyMatch(p -> p instanceof HearingAidProfile);
+ }
+
+ private HearingAidInfo generateHearingAidInfo(CachedBluetoothDevice cachedDevice) {
+ final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
+ if (isAshaHearingAid(cachedDevice)) {
+ final HearingAidProfile asha = profileManager.getHearingAidProfile();
+ if (asha == null) {
+ Log.w(TAG, "HearingAidProfile is not supported on this device");
+ } else {
+ long hiSyncId = asha.getHiSyncId(cachedDevice.getDevice());
+ if (isValidHiSyncId(hiSyncId)) {
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setAshaDeviceSide(asha.getDeviceSide(cachedDevice.getDevice()))
+ .setAshaDeviceMode(asha.getDeviceMode(cachedDevice.getDevice()))
+ .setHiSyncId(hiSyncId);
+ return infoBuilder.build();
+ }
+ }
+ }
+ if (isLeAudioHearingAid(cachedDevice)) {
+ final HapClientProfile hapClientProfile = profileManager.getHapClientProfile();
+ final LeAudioProfile leAudioProfile = profileManager.getLeAudioProfile();
+ if (hapClientProfile == null || leAudioProfile == null) {
+ Log.w(TAG, "HapClientProfile or LeAudioProfile is not supported on this device");
+ } else {
+ int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice());
+ int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice());
+ if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID
+ && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) {
+ final HearingAidInfo.Builder infoBuilder = new HearingAidInfo.Builder()
+ .setLeAudioLocation(audioLocation)
+ .setHapDeviceType(hearingAidType);
+ return infoBuilder.build();
+ }
+ }
+ }
+ return null;
+ }
+
private void log(String msg) {
if (DEBUG) {
Log.d(TAG, msg);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index 14fab16de3e6..f2450de60878 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -109,7 +109,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
device.refresh();
}
- // Check current list of CachedDevices to see if any are Hearing Aid devices.
+ // Check current list of CachedDevices to see if any are hearing aid devices.
mDeviceManager.updateHearingAidsDevices();
mIsProfileReady = true;
mProfileManager.callServiceConnectedListeners();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 931a6f149b84..6be4336178eb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -83,6 +83,8 @@ public class LeAudioProfile implements LocalBluetoothProfile {
device.refresh();
}
+ // Check current list of CachedDevices to see if any are hearing aid devices.
+ mDeviceManager.updateHearingAidsDevices();
mProfileManager.callServiceConnectedListeners();
mIsProfileReady = true;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 934870507a20..1d2f7902b781 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -759,6 +759,20 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
/**
+ * Update the LE Broadcast by calling {@link BluetoothLeBroadcast#updateBroadcast(int,
+ * BluetoothLeAudioContentMetadata)}, currently only updates programInfo.
+ */
+ public void updateBroadcast() {
+ if (mServiceBroadcast == null) {
+ Log.d(TAG, "The BluetoothLeBroadcast is null when updating the broadcast.");
+ return;
+ }
+ String programInfo = getProgramInfo();
+ mBluetoothLeAudioContentMetadata = mBuilder.setProgramInfo(programInfo).build();
+ mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
+ }
+
+ /**
* Register Broadcast Callbacks to track its state and receivers
*
* @param executor Executor object for callback
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index cf224dc3be5f..3de49336f427 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -71,15 +71,15 @@ public class DeviceIconUtil {
new Device(
AudioDeviceInfo.TYPE_HDMI,
MediaRoute2Info.TYPE_HDMI,
- mIsTv ? R.drawable.ic_tv : R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_tv : R.drawable.ic_external_display),
new Device(
AudioDeviceInfo.TYPE_HDMI_ARC,
MediaRoute2Info.TYPE_HDMI_ARC,
- mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
new Device(
AudioDeviceInfo.TYPE_HDMI_EARC,
MediaRoute2Info.TYPE_HDMI_EARC,
- mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_headphone),
+ mIsTv ? R.drawable.ic_hdmi : R.drawable.ic_external_display),
new Device(
AudioDeviceInfo.TYPE_WIRED_HEADSET,
MediaRoute2Info.TYPE_WIRED_HEADSET,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index 8a122fcddcb3..aef09ac236f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -26,11 +26,13 @@ import android.media.MediaRouter2Manager;
import android.media.RouteDiscoveryPreference;
import android.media.RouteListingPreference;
import android.media.RoutingSessionInfo;
+import android.os.Process;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.media.flags.Flags;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import java.util.ArrayList;
@@ -62,21 +64,33 @@ public final class RouterInfoMediaManager extends InfoMediaManager {
refreshDevices();
};
- // TODO: b/192657812 - Create factory method in InfoMediaManager to return
- // RouterInfoMediaManager or ManagerInfoMediaManager based on flag.
+ // TODO (b/321969740): Plumb target UserHandle between UMO and RouterInfoMediaManager.
public RouterInfoMediaManager(
Context context,
String packageName,
Notification notification,
- LocalBluetoothManager localBluetoothManager) throws PackageNotAvailableException {
+ LocalBluetoothManager localBluetoothManager)
+ throws PackageNotAvailableException {
super(context, packageName, notification, localBluetoothManager);
- mRouter = MediaRouter2.getInstance(context, packageName);
+ MediaRouter2 router = null;
- if (mRouter == null) {
+ if (Flags.enableCrossUserRoutingInMediaRouter2()) {
+ try {
+ router = MediaRouter2.getInstance(context, packageName, Process.myUserHandle());
+ } catch (IllegalArgumentException ex) {
+ // Do nothing
+ }
+ } else {
+ router = MediaRouter2.getInstance(context, packageName);
+ }
+ if (router == null) {
throw new PackageNotAvailableException(
"Package name " + packageName + " does not exist.");
}
+ // We have to defer initialization because mRouter is final.
+ mRouter = router;
+
mRouterManager = MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
index cd5f59731e7f..b015b2bce60a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserInfoController.java
@@ -234,6 +234,6 @@ public class EditUserInfoController {
EditUserPhotoController createEditUserPhotoController(Activity activity,
ActivityStarter activityStarter, ImageView userPhotoView) {
return new EditUserPhotoController(activity, activityStarter, userPhotoView,
- mSavedPhoto, mSavedDrawable, mFileAuthority);
+ mSavedPhoto, mSavedDrawable, mFileAuthority, false);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index e83b9bc25799..b2de5a948836 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -62,6 +62,7 @@ public class EditUserPhotoController {
private static final String AVATAR_PICKER_ACTION = "com.android.avatarpicker"
+ ".FULL_SCREEN_ACTIVITY";
+ static final String EXTRA_IS_USER_NEW = "is_user_new";
private final Activity mActivity;
private final ActivityStarter mActivityStarter;
@@ -72,9 +73,13 @@ public class EditUserPhotoController {
private Bitmap mNewUserPhotoBitmap;
private Drawable mNewUserPhotoDrawable;
private String mCachedDrawablePath;
-
public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority) {
+ this(activity, activityStarter, view, savedBitmap, savedDrawable, fileAuthority, true);
+ }
+ public EditUserPhotoController(Activity activity, ActivityStarter activityStarter,
+ ImageView view, Bitmap savedBitmap, Drawable savedDrawable, String fileAuthority,
+ boolean isUserNew) {
mActivity = activity;
mActivityStarter = activityStarter;
mFileAuthority = fileAuthority;
@@ -82,7 +87,7 @@ public class EditUserPhotoController {
mImagesDir = new File(activity.getCacheDir(), IMAGES_DIR);
mImagesDir.mkdir();
mImageView = view;
- mImageView.setOnClickListener(v -> showAvatarPicker());
+ mImageView.setOnClickListener(v -> showAvatarPicker(isUserNew));
mNewUserPhotoBitmap = savedBitmap;
mNewUserPhotoDrawable = savedDrawable;
@@ -117,11 +122,12 @@ public class EditUserPhotoController {
return mNewUserPhotoDrawable;
}
- private void showAvatarPicker() {
+ private void showAvatarPicker(boolean isUserNew) {
Intent intent;
if (Flags.avatarSync()) {
intent = new Intent(AVATAR_PICKER_ACTION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.putExtra(EXTRA_IS_USER_NEW, isUserNew);
} else {
intent = new Intent(mImageView.getContext(), AvatarPickerActivity.class);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
new file mode 100644
index 000000000000..3355fb395ca0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import android.media.AudioManager
+import com.android.internal.util.ConcurrentUtils
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Provides audio managing functionality and data. */
+interface AudioRepository {
+
+ /** Current [AudioManager.getMode]. */
+ val mode: StateFlow<Int>
+}
+
+class AudioRepositoryImpl(
+ private val audioManager: AudioManager,
+ backgroundCoroutineContext: CoroutineContext,
+ coroutineScope: CoroutineScope,
+) : AudioRepository {
+
+ override val mode: StateFlow<Int> =
+ callbackFlow {
+ val listener =
+ AudioManager.OnModeChangedListener { newMode -> launch { send(newMode) } }
+ audioManager.addOnModeChangedListener(ConcurrentUtils.DIRECT_EXECUTOR, listener)
+ awaitClose { audioManager.removeOnModeChangedListener(listener) }
+ }
+ .flowOn(backgroundCoroutineContext)
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), audioManager.mode)
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt
new file mode 100644
index 000000000000..053c59b15067
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractor.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class AudioModeInteractor(repository: AudioRepository) {
+
+ private val ongoingCallModes =
+ setOf(
+ AudioManager.MODE_RINGTONE,
+ AudioManager.MODE_IN_CALL,
+ AudioManager.MODE_IN_COMMUNICATION,
+ )
+
+ /** Returns if current [AudioManager.getMode] call is an ongoing call */
+ val isOngoingCall: Flow<Boolean> = repository.mode.map { it in ongoingCallModes }
+}
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 644b72c383ac..ce3a7baf6be6 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -57,6 +57,7 @@ android_test {
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
"settingslib_media_flags_lib",
+ "kotlinx_coroutines_test",
],
dxflags: ["--multi-dex"],
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
new file mode 100644
index 000000000000..686362fadf02
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeAudioRepository : AudioRepository {
+
+ private val mutableMode = MutableStateFlow(0)
+ override val mode: StateFlow<Int>
+ get() = mutableMode.asStateFlow()
+
+ fun setMode(newMode: Int) {
+ mutableMode.value = newMode
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
new file mode 100644
index 000000000000..3bc1edc9c944
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/domain/interactor/AudioModeInteractorTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.volume.domain.interactor
+
+import android.media.AudioManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.settingslib.volume.data.repository.FakeAudioRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class AudioModeInteractorTest {
+
+ private val testScope = TestScope()
+ private val fakeAudioRepository = FakeAudioRepository()
+
+ private val underTest = AudioModeInteractor(fakeAudioRepository)
+
+ @Test
+ fun ongoingCallModes_isOnGoingCall() {
+ testScope.runTest {
+ for (mode in ongoingCallModes) {
+ var isOngoingCall = false
+ underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)
+
+ fakeAudioRepository.setMode(mode)
+ runCurrent()
+
+ assertThat(isOngoingCall).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun notOngoingCallModes_isNotOnGoingCall() {
+ testScope.runTest {
+ var isOngoingCall = true
+ underTest.isOngoingCall.onEach { isOngoingCall = it }.launchIn(backgroundScope)
+
+ fakeAudioRepository.setMode(AudioManager.MODE_CURRENT)
+ runCurrent()
+
+ assertThat(isOngoingCall).isFalse()
+ }
+ }
+
+ private companion object {
+ private val ongoingCallModes =
+ setOf(
+ AudioManager.MODE_RINGTONE,
+ AudioManager.MODE_IN_CALL,
+ AudioManager.MODE_IN_COMMUNICATION,
+ )
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index e7487e857464..aa5a2984e70c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -15,6 +15,15 @@
*/
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT;
+import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_BINAURAL;
+import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_INVALID;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceMode.MODE_BINAURAL;
+import static com.android.settingslib.bluetooth.HearingAidProfile.DeviceSide.SIDE_RIGHT;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -32,7 +41,6 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.ScanFilter;
@@ -92,6 +100,10 @@ public class HearingAidDeviceManagerTest {
@Mock
private HearingAidProfile mHearingAidProfile;
@Mock
+ private LeAudioProfile mLeAudioProfile;
+ @Mock
+ private HapClientProfile mHapClientProfile;
+ @Mock
private AudioProductStrategy mAudioStrategy;
@Mock
private BluetoothDevice mDevice1;
@@ -123,6 +135,8 @@ public class HearingAidDeviceManagerTest {
when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+ when(mLocalProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
+ when(mLocalProfileManager.getHapClientProfile()).thenReturn(mHapClientProfile);
when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
AudioManager.STREAM_MUSIC))
.thenReturn((new AudioAttributes.Builder()).build());
@@ -140,34 +154,43 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test initHearingAidDeviceIfNeeded, set HearingAid's information, including HiSyncId,
- * deviceSide, deviceMode.
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) ASHA hearing aid
+ * 2) Valid HiSyncId
+ * Result:
+ * Set hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_validHiSyncId_setHearingAidInfo() {
+ public void initHearingAidDeviceIfNeeded_asha_validHiSyncId_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
- when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
- HearingAidProfile.DeviceMode.MODE_BINAURAL);
- when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+ when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
assertThat(mCachedDevice1.getHiSyncId()).isNotEqualTo(HISYNCID1);
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1);
+ assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
HearingAidInfo.DeviceMode.MODE_BINAURAL);
- assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(
- HearingAidInfo.DeviceSide.SIDE_RIGHT);
}
/**
- * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId will not be assigned
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) ASHA hearing aid
+ * 2) Invalid HiSyncId
+ * Result:
+ * Do not set hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_invalidHiSyncId_notToSetHearingAidInfo() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
- BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void initHearingAidDeviceIfNeeded_asha_invalidHiSyncId_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
@@ -175,34 +198,89 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and hearing aid scan filter, set an
- * empty hearing aid info on the device.
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) ASHA hearing aid
+ * 2) Invalid HiSyncId
+ * 3) ASHA uuid scan filter
+ * Result:
+ * Set an empty hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_hearingAidScanFilter_setHearingAidInfo() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
- BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void initHearingAidDeviceIfNeeded_asha_scanFilterNotNull_setEmptyHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
final ScanFilter scanFilter = new ScanFilter.Builder()
.setServiceData(BluetoothUuid.HEARING_AID, new byte[]{0}).build();
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
- assertThat(mCachedDevice1.isHearingAidDevice()).isTrue();
+ verify(mCachedDevice1).setHearingAidInfo(new HearingAidInfo.Builder().build());
}
/**
- * Test initHearingAidDeviceIfNeeded, an invalid HiSyncId and random scan filter, not to set
- * hearing aid info on the device.
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) Asha hearing aid
+ * 2) Invalid HiSyncId
+ * 3) Random scan filter
+ * Result:
+ * Do not set hearing aid info to the device.
*/
@Test
- public void initHearingAidDeviceIfNeeded_randomScanFilter_setHearingAidInfo() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(
- BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void initHearingAidDeviceIfNeeded_asha_randomScanFilter_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
final ScanFilter scanFilter = new ScanFilter.Builder().build();
mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, List.of(scanFilter));
- assertThat(mCachedDevice1.isHearingAidDevice()).isFalse();
+ verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+ }
+
+ /**
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Valid audio location and device type
+ * Result:
+ * Set hearing aid info to the device.
+ */
+ @Test
+ public void initHearingAidDeviceIfNeeded_leAudio_validInfo_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+ verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+ assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+ assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+ HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ }
+
+ /**
+ * Test initHearingAidDeviceIfNeeded
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Invalid audio location and device type
+ * Result:
+ * Do not set hearing aid info to the device.
+ */
+ @Test
+ public void initHearingAidDeviceIfNeeded_leAudio_invalidInfo_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+
+ mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
+
+ verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
}
/**
@@ -234,13 +312,20 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
- * When first paired devices is connected and second paired device is disconnected, first
- * paired device would be set as main device and second device will be removed from
- * CachedDevices list.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * 2) First paired devices is connected
+ * 3) Second paired device is disconnected
+ * Result:
+ * First paired device would be set as main device and second paired device will be set
+ * as sub device and removed from CachedDevices list.
*/
@Test
- public void updateHearingAidsDevices_firstPairedDevicesConnected_verifySubDevice() {
+ public void updateHearingAidsDevices_asha_firstPairedDevicesConnected_verifySubDevice() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -257,13 +342,20 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
- * When second paired devices is connected and first paired device is disconnected, second
- * paired device would be set as main device and first device will be removed from
- * CachedDevices list.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * 2) First paired devices is disconnected
+ * 3) Second paired device is connected
+ * Result:
+ * Second paired device would be set as main device and first paired device will be set
+ * as sub device and removed from CachedDevices list.
*/
@Test
- public void updateHearingAidsDevices_secondPairedDeviceConnected_verifySubDevice() {
+ public void updateHearingAidsDevices_asha_secondPairedDeviceConnected_verifySubDevice() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
when(mCachedDevice1.isConnected()).thenReturn(false);
@@ -280,12 +372,20 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, to link two devices with the same HiSyncId.
- * When both devices are connected, to build up main and sub relationship and to remove sub
- * device from CachedDevices list.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * 2) First paired devices is connected
+ * 3) Second paired device is connected
+ * Result:
+ * First paired device would be set as main device and second paired device will be set
+ * as sub device and removed from CachedDevices list.
*/
@Test
- public void updateHearingAidsDevices_BothConnected_verifySubDevice() {
+ public void updateHearingAidsDevices_asha_bothConnected_verifySubDevice() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
when(mCachedDevice1.isConnected()).thenReturn(true);
@@ -302,46 +402,64 @@ public class HearingAidDeviceManagerTest {
}
/**
- * Test updateHearingAidsDevices, dispatch callback
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with the same HiSyncId
+ * Result:
+ * Dispatch device removed callback
*/
@Test
- public void updateHearingAidsDevices_dispatchDeviceRemovedCallback() {
+ public void updateHearingAidsDevices_asha_dispatchDeviceRemovedCallback() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HISYNCID1);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
mHearingAidDeviceManager.updateHearingAidsDevices();
verify(mBluetoothEventManager).dispatchDeviceRemoved(mCachedDevice1);
}
/**
- * Test updateHearingAidsDevices, do nothing when HiSyncId is invalid
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) Two ASHA hearing aids with invalid HiSyncId
+ * Result:
+ * Do nothing
*/
@Test
- public void updateHearingAidsDevices_invalidHiSyncId_doNothing() {
- when(mHearingAidProfile.getHiSyncId(mDevice1)).
- thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
- when(mHearingAidProfile.getHiSyncId(mDevice2)).
- thenReturn(BluetoothHearingAid.HI_SYNC_ID_INVALID);
+ public void updateHearingAidsDevices_asha_invalidHiSyncId_doNothing() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mCachedDevice2.getProfiles()).thenReturn(List.of(mHearingAidProfile));
+ when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HI_SYNC_ID_INVALID);
+ when(mHearingAidProfile.getHiSyncId(mDevice2)).thenReturn(HI_SYNC_ID_INVALID);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice2);
+
mHearingAidDeviceManager.updateHearingAidsDevices();
verify(mHearingAidDeviceManager, never()).onHiSyncIdChanged(anyLong());
}
/**
- * Test updateHearingAidsDevices, set HearingAid's information, including HiSyncId, deviceSide,
- * deviceMode.
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) ASHA hearing aids
+ * 2) Valid HiSync Id
+ * Result:
+ * Set hearing aid info to the device.
*/
@Test
- public void updateHearingAidsDevices_validHiSyncId_setHearingAidInfos() {
+ public void updateHearingAidsDevices_asha_validHiSyncId_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mHearingAidProfile));
when(mHearingAidProfile.getHiSyncId(mDevice1)).thenReturn(HISYNCID1);
- when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(
- HearingAidProfile.DeviceMode.MODE_BINAURAL);
- when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(
- HearingAidProfile.DeviceSide.SIDE_RIGHT);
+ when(mHearingAidProfile.getDeviceMode(mDevice1)).thenReturn(MODE_BINAURAL);
+ when(mHearingAidProfile.getDeviceSide(mDevice1)).thenReturn(SIDE_RIGHT);
mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
mHearingAidDeviceManager.updateHearingAidsDevices();
@@ -355,6 +473,51 @@ public class HearingAidDeviceManagerTest {
}
/**
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Valid audio location and device type
+ * Result:
+ * Set hearing aid info to the device.
+ */
+ @Test
+ public void updateHearingAidsDevices_leAudio_validInfo_setHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_BINAURAL);
+ mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+ mHearingAidDeviceManager.updateHearingAidsDevices();
+
+ verify(mCachedDevice1).setHearingAidInfo(any(HearingAidInfo.class));
+ assertThat(mCachedDevice1.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_LEFT);
+ assertThat(mCachedDevice1.getDeviceMode()).isEqualTo(
+ HearingAidInfo.DeviceMode.MODE_BINAURAL);
+ }
+
+ /**
+ * Test updateHearingAidsDevices
+ *
+ * Conditions:
+ * 1) LeAudio hearing aid
+ * 2) Invalid audio location and device type
+ * Result:
+ * Do not set hearing aid info to the device.
+ */
+ @Test
+ public void updateHearingAidsDevices_leAudio_invalidInfo_notToSetHearingAidInfo() {
+ when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
+ when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+ when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
+ mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
+
+ mHearingAidDeviceManager.updateHearingAidsDevices();
+
+ verify(mCachedDevice1, never()).setHearingAidInfo(any(HearingAidInfo.class));
+ }
+
+ /**
* Test onProfileConnectionStateChangedIfProcessed.
* When first hearing aid device is connected, to process it same as other generic devices.
* No need to process it.
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 89a8dd95d3c3..17d9f1b87fac 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -335,4 +335,7 @@
<!-- Default for Settings.BATTERY_CHARGING_STATE_ENFORCE_LEVEL.
-1 means system internal default value is used. -->
<integer name="def_battery_charging_state_enforce_level">-1</integer>
+
+ <!-- Value to use as default scale for fonts -->
+ <item name="def_device_font_scale" format="float" type="dimen">1.0</item>
</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8ae50eb7ffad..8ad5f244b659 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -74,6 +74,7 @@ public class SecureSettings {
Settings.Secure.TTS_DEFAULT_LOCALE,
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS,
+ Settings.Secure.ACCESSIBILITY_SLOW_KEYS,
Settings.Secure.ACCESSIBILITY_STICKY_KEYS,
Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, // moved to global
Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, // moved to global
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index e7d7bb01e180..38ec931612f0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -48,6 +48,7 @@ public class SystemSettings {
Settings.System.WIFI_STATIC_DNS2,
Settings.System.BLUETOOTH_DISCOVERABILITY,
Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ Settings.System.DEFAULT_DEVICE_FONT_SCALE,
Settings.System.FONT_SCALE,
Settings.System.DIM_SCREEN,
Settings.System.SCREEN_OFF_TIMEOUT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 285c8c969343..d854df38a9ef 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -120,6 +120,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.TTS_DEFAULT_LOCALE, TTS_LIST_VALIDATOR);
VALIDATORS.put(Secure.SHOW_IME_WITH_HARD_KEYBOARD, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_BOUNCE_KEYS, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.ACCESSIBILITY_SLOW_KEYS, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_STICKY_KEYS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index a8a659ee1e5c..677c81ad9271 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -45,6 +45,9 @@ public class SettingsValidators {
}
};
+ public static final Validator FONT_SCALE_VALIDATOR = new InclusiveFloatRangeValidator(0.25f,
+ 5.0f);
+
public static final Validator NON_NEGATIVE_INTEGER_VALIDATOR = new Validator() {
@Override
public boolean validate(@Nullable String value) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 572303a813bf..98941c7cc116 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -20,6 +20,7 @@ import static android.provider.settings.validators.SettingsValidators.ANY_INTEGE
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;
+import static android.provider.settings.validators.SettingsValidators.FONT_SCALE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.LENIENT_IP_ADDRESS_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_FLOAT_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.NON_NEGATIVE_INTEGER_VALIDATOR;
@@ -31,7 +32,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.hardware.display.ColorDisplayManager;
import android.os.BatteryManager;
-import android.provider.Settings.Global;
import android.provider.Settings.System;
import android.util.ArrayMap;
@@ -93,7 +93,8 @@ public class SystemSettingsValidators {
return value == null || value.length() < MAX_LENGTH;
}
});
- VALIDATORS.put(System.FONT_SCALE, new InclusiveFloatRangeValidator(0.25f, 5.0f));
+ VALIDATORS.put(System.DEFAULT_DEVICE_FONT_SCALE, FONT_SCALE_VALIDATOR);
+ VALIDATORS.put(System.FONT_SCALE, FONT_SCALE_VALIDATOR);
VALIDATORS.put(System.DIM_SCREEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(
System.DISPLAY_COLOR_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3a46f4e96ccb..febce97031bb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3812,7 +3812,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 224;
+ private static final int SETTINGS_VERSION = 225;
private final int mUserId;
@@ -6004,6 +6004,13 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 224;
}
+ // Version 224: Update the default font scale depending on the
+ // R.dimen.def_device_font_scale configuration property.
+ if (currentVersion == 224) {
+ handleDefaultFontScale(getSystemSettingsLocked(userId));
+ currentVersion = 225;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
@@ -6021,6 +6028,32 @@ public class SettingsProvider extends ContentProvider {
return currentVersion;
}
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mLock")
+ private void handleDefaultFontScale(@NonNull SettingsState systemSettings) {
+ final float defaultFontScale = getContext().getResources()
+ .getFloat(R.dimen.def_device_font_scale);
+ // Persist the value for future use (e.g. Reset Settings option)
+ systemSettings.insertSettingLocked(
+ Settings.System.DEFAULT_DEVICE_FONT_SCALE,
+ String.valueOf(defaultFontScale),
+ /* tag= */ null,
+ /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ // We verify if there is a pre existing value for font_scale.
+ final Setting existingFontScale = systemSettings.getSettingLocked(
+ Settings.System.FONT_SCALE);
+ if (existingFontScale == null || existingFontScale.isNull()) {
+ // Set the default value only if it didn't exist before
+ systemSettings.insertSettingLocked(
+ Settings.System.FONT_SCALE,
+ String.valueOf(defaultFontScale),
+ /* tag= */ null,
+ /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
@GuardedBy("mLock")
private void initGlobalSettingsDefaultValLocked(String key, boolean val) {
initGlobalSettingsDefaultValLocked(key, val ? "1" : "0");
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d38454221f76..cdb4aea6ee79 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -561,7 +561,7 @@
<uses-permission android:name="android.permission.TEST_BIOMETRIC" />
<!-- Permission required for CTS test - android.server.biometrics -->
- <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+ <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
@@ -902,6 +902,9 @@
<!-- Permission required for BinaryTransparencyService shell API and host test -->
<uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+ <!-- Permissions required for CTS test - CtsPermissionUiTestCases -->
+ <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 7ba889bc8fee..866aa8945525 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -17,6 +17,13 @@ flag {
}
flag {
+ name: "floating_menu_drag_to_edit"
+ namespace: "accessibility"
+ description: "adds a second drag button to allow the user edit the shortcut."
+ bug: "297583708"
+}
+
+flag {
name: "floating_menu_ime_displacement_animation"
namespace: "accessibility"
description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2c35c777ab12..a2530d59e2e6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -364,3 +364,10 @@ flag {
description: "Enables styled focus states on pin input field if keyboard is connected"
bug: "316106516"
}
+
+flag {
+ name: "keyguard_wm_state_refactor"
+ namespace: "systemui"
+ description: "Enables refactored logic for SysUI+WM unlock/occlusion code paths"
+ bug: "278086361"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index a22fecf3688d..4fdcf7579dc6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -84,13 +84,14 @@ fun CommunalContainer(
SceneTransitionLayout(
state = sceneTransitionLayoutState,
modifier = modifier.fillMaxSize(),
- edgeDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
+ swipeSourceDetector = FixedSizeEdgeDetector(ContainerDimensions.EdgeSwipeSize),
) {
scene(
TransitionSceneKey.Blank,
userActions =
mapOf(
- Swipe(SwipeDirection.Left, fromEdge = Edge.Right) to TransitionSceneKey.Communal
+ Swipe(SwipeDirection.Left, fromSource = Edge.Right) to
+ TransitionSceneKey.Communal
)
) {
// This scene shows nothing only allowing for transitions to the communal scene.
@@ -101,7 +102,7 @@ fun CommunalContainer(
TransitionSceneKey.Communal,
userActions =
mapOf(
- Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TransitionSceneKey.Blank
+ Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TransitionSceneKey.Blank
),
) {
CommunalScene(viewModel, modifier = modifier)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 409f15bb4bb8..761e74e52237 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -97,11 +97,13 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
import androidx.core.view.setPadding
+import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -132,6 +134,8 @@ fun CommunalHub(
val removeButtonEnabled by remember {
derivedStateOf { selectedIndex.value != null || reorderingWidgets }
}
+ val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) =
+ remember { mutableStateOf(false) }
val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
val contentOffset = beforeContentPadding(contentPadding).toOffset()
@@ -158,6 +162,11 @@ fun CommunalHub(
}
viewModel.setSelectedIndex(newIndex)
}
+ }
+ .thenIf(!viewModel.isEditMode) {
+ Modifier.pointerInput(Unit) {
+ detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) }
+ }
},
) {
CommunalHubLazyGrid(
@@ -207,6 +216,16 @@ fun CommunalHub(
PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta)
}
+ if (isButtonToEditWidgetsShowing) {
+ ButtonToEditWidgets(
+ onClick = {
+ setIsButtonToEditWidgetsShowing(false)
+ viewModel.onOpenWidgetEditor()
+ },
+ onHide = { setIsButtonToEditWidgetsShowing(false) },
+ )
+ }
+
// This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving
// touches, so that the SceneTransitionLayout can intercept the touches and allow an edge
// swipe back to the blank scene.
@@ -414,6 +433,34 @@ private fun Toolbar(
}
@Composable
+private fun ButtonToEditWidgets(
+ onClick: () -> Unit,
+ onHide: () -> Unit,
+) {
+ Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) {
+ val colors = LocalAndroidColorScheme.current
+ Button(
+ modifier =
+ Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)),
+ onClick = onClick,
+ ) {
+ Icon(
+ imageVector = Icons.Outlined.Widgets,
+ contentDescription = stringResource(R.string.button_to_configure_widgets_text),
+ tint = colors.onSecondary,
+ modifier = Modifier.size(20.dp)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(R.string.button_to_configure_widgets_text),
+ style = MaterialTheme.typography.titleSmall,
+ color = colors.onSecondary,
+ )
+ }
+ }
+}
+
+@Composable
private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) {
Popup(
alignment = Alignment.TopCenter,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
index 14074944259b..bc1e429e57cf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
@@ -20,9 +20,13 @@ import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastForEach
import kotlinx.coroutines.coroutineScope
/**
@@ -44,6 +48,41 @@ suspend fun PointerInputScope.observeTapsWithoutConsuming(
}
}
+/**
+ * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an
+ * Offset representing the position relative to the containing element.
+ */
+suspend fun PointerInputScope.detectLongPressGesture(
+ pass: PointerEventPass = PointerEventPass.Initial,
+ onLongPress: ((Offset) -> Unit),
+) = coroutineScope {
+ awaitEachGesture {
+ val down = awaitFirstDown(pass = pass)
+ val longPressTimeout = viewConfiguration.longPressTimeoutMillis
+ // wait for first tap up or long press
+ try {
+ withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) }
+ } catch (_: PointerEventTimeoutCancellationException) {
+ // withTimeout throws exception if timeout has passed before block completes
+ onLongPress.invoke(down.position)
+ consumeUntilUp(pass)
+ }
+ }
+}
+
+/**
+ * Consumes all pointer events until nothing is pressed and then returns. This method assumes that
+ * something is currently pressed.
+ */
+private suspend fun AwaitPointerEventScope.consumeUntilUp(
+ pass: PointerEventPass = PointerEventPass.Initial
+) {
+ do {
+ val event = awaitPointerEvent(pass = pass)
+ event.changes.fastForEach { it.consume() }
+ } while (event.changes.fastAny { it.pressed })
+}
+
/** Consume all gestures on the initial pass so that child elements do not receive them. */
suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
awaitEachGesture {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index 56d6879e614e..bf02d8abf73c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -173,7 +173,8 @@ constructor(
val belowLockIconPlaceable =
belowLockIconMeasurable.measure(
noMinConstraints.copy(
- maxHeight = constraints.maxHeight - lockIconBounds.bottom
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
)
)
val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
index fdf11668ae76..616a7b4752a0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/SplitShadeBlueprint.kt
@@ -16,19 +16,42 @@
package com.android.systemui.keyguard.ui.composable.blueprint
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
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.layout.Layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
+import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
+import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
+import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
+import com.android.systemui.keyguard.ui.composable.section.ClockSection
+import com.android.systemui.keyguard.ui.composable.section.LockSection
+import com.android.systemui.keyguard.ui.composable.section.NotificationSection
+import com.android.systemui.keyguard.ui.composable.section.SettingsMenuSection
+import com.android.systemui.keyguard.ui.composable.section.SmartSpaceSection
+import com.android.systemui.keyguard.ui.composable.section.StatusBarSection
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
+import com.android.systemui.res.R
+import com.android.systemui.shade.LargeScreenHeaderHelper
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
+import java.util.Optional
import javax.inject.Inject
/**
@@ -39,22 +62,174 @@ class SplitShadeBlueprint
@Inject
constructor(
private val viewModel: LockscreenContentViewModel,
+ private val statusBarSection: StatusBarSection,
+ private val clockSection: ClockSection,
+ private val smartSpaceSection: SmartSpaceSection,
+ private val notificationSection: NotificationSection,
+ private val lockSection: LockSection,
+ private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
+ private val bottomAreaSection: BottomAreaSection,
+ private val settingsMenuSection: SettingsMenuSection,
+ private val clockInteractor: KeyguardClockInteractor,
+ private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
) : LockscreenSceneBlueprint {
override val id: String = "split-shade"
@Composable
override fun SceneScope.Content(modifier: Modifier) {
+ val isUdfpsVisible = viewModel.isUdfpsVisible
+ val burnIn = rememberBurnIn(clockInteractor)
+ val resources = LocalContext.current.resources
+
LockscreenLongPress(
viewModel = viewModel.longPress,
modifier = modifier,
- ) { _ ->
- Box(modifier.background(Color.Black)) {
- Text(
- text = "TODO(b/316211368): split shade blueprint",
- color = Color.White,
- modifier = Modifier.align(Alignment.Center),
- )
+ ) { onSettingsMenuPlaced ->
+ Layout(
+ content = {
+ // Constrained to above the lock icon.
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
+ Row(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Column(
+ modifier = Modifier.fillMaxHeight().weight(weight = 1f),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ top = {
+ viewModel.getSmartSpacePaddingTop(resources)
+ }
+ ),
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ with(clockSection) { LargeClock() }
+ Spacer(modifier = Modifier.weight(weight = 1f))
+ }
+ with(notificationSection) {
+ val splitShadeTopMargin: Dp =
+ if (Flags.centralizedStatusBarDimensRefactor()) {
+ largeScreenHeaderHelper.getLargeScreenHeaderHeight().dp
+ } else {
+ dimensionResource(
+ id = R.dimen.large_screen_shade_header_height
+ )
+ }
+ Notifications(
+ modifier =
+ Modifier.fillMaxHeight()
+ .weight(weight = 1f)
+ .padding(top = splitShadeTopMargin)
+ )
+ }
+ }
+
+ if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+ }
+
+ with(lockSection) { LockIcon() }
+
+ // Aligned to bottom and constrained to below the lock icon.
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+ with(ambientIndicationSectionOptional.get()) {
+ AmbientIndication(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ with(bottomAreaSection) {
+ IndicationArea(modifier = Modifier.fillMaxWidth())
+ }
+ }
+
+ // Aligned to bottom and NOT constrained by the lock icon.
+ with(bottomAreaSection) {
+ Shortcut(isStart = true, applyPadding = true)
+ Shortcut(isStart = false, applyPadding = true)
+ }
+ with(settingsMenuSection) { SettingsMenu(onSettingsMenuPlaced) }
+ },
+ modifier = Modifier.fillMaxSize(),
+ ) { measurables, constraints ->
+ check(measurables.size == 6)
+ val aboveLockIconMeasurable = measurables[0]
+ val lockIconMeasurable = measurables[1]
+ val belowLockIconMeasurable = measurables[2]
+ val startShortcutMeasurable = measurables[3]
+ val endShortcutMeasurable = measurables[4]
+ val settingsMenuMeasurable = measurables[5]
+
+ val noMinConstraints =
+ constraints.copy(
+ minWidth = 0,
+ minHeight = 0,
+ )
+ val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints)
+ val lockIconBounds =
+ IntRect(
+ left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left],
+ top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top],
+ right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right],
+ bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom],
+ )
+
+ val aboveLockIconPlaceable =
+ aboveLockIconMeasurable.measure(
+ noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ )
+ val belowLockIconPlaceable =
+ belowLockIconMeasurable.measure(
+ noMinConstraints.copy(
+ maxHeight =
+ (constraints.maxHeight - lockIconBounds.bottom).coerceAtLeast(0)
+ )
+ )
+ val startShortcutPleaceable = startShortcutMeasurable.measure(noMinConstraints)
+ val endShortcutPleaceable = endShortcutMeasurable.measure(noMinConstraints)
+ val settingsMenuPlaceable = settingsMenuMeasurable.measure(noMinConstraints)
+
+ layout(constraints.maxWidth, constraints.maxHeight) {
+ aboveLockIconPlaceable.place(
+ x = 0,
+ y = 0,
+ )
+ lockIconPlaceable.place(
+ x = lockIconBounds.left,
+ y = lockIconBounds.top,
+ )
+ belowLockIconPlaceable.place(
+ x = 0,
+ y = constraints.maxHeight - belowLockIconPlaceable.height,
+ )
+ startShortcutPleaceable.place(
+ x = 0,
+ y = constraints.maxHeight - startShortcutPleaceable.height,
+ )
+ endShortcutPleaceable.place(
+ x = constraints.maxWidth - endShortcutPleaceable.width,
+ y = constraints.maxHeight - endShortcutPleaceable.height,
+ )
+ settingsMenuPlaceable.place(
+ x = (constraints.maxWidth - settingsMenuPlaceable.width) / 2,
+ y = constraints.maxHeight - settingsMenuPlaceable.height,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index f40b871e923c..8f218792ee32 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -16,7 +16,8 @@
package com.android.systemui.keyguard.ui.composable.section
-import androidx.compose.foundation.layout.fillMaxWidth
+import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -75,7 +76,13 @@ constructor(
) {
content {
AndroidView(
- factory = { checkNotNull(currentClock).smallClock.view },
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
modifier =
Modifier.padding(
horizontal =
@@ -83,6 +90,12 @@ constructor(
)
.padding(top = { viewModel.getSmallClockTopMargin(view.context) })
.onTopPlacementChanged(onTopChanged),
+ update = {
+ val newClockView = checkNotNull(currentClock).smallClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
)
}
}
@@ -116,8 +129,19 @@ constructor(
) {
content {
AndroidView(
- factory = { checkNotNull(currentClock).largeClock.view },
- modifier = Modifier.fillMaxWidth()
+ factory = { context ->
+ FrameLayout(context).apply {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ addView(newClockView)
+ }
+ },
+ update = {
+ val newClockView = checkNotNull(currentClock).largeClock.view
+ it.removeAllViews()
+ (newClockView.parent as? ViewGroup)?.removeView(newClockView)
+ it.addView(newClockView)
+ },
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 2a6bea75791c..be6f022d8d52 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -33,6 +33,7 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
@@ -47,10 +48,12 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
class LockSection
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
@@ -76,6 +79,7 @@ constructor(
DeviceEntryIconView(context, null).apply {
id = R.id.device_entry_icon_view
DeviceEntryIconViewBinder.bind(
+ applicationScope,
this,
deviceEntryIconViewModel.get(),
deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index c35202cd830a..9f9e1f5bb56a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -183,7 +183,7 @@ private fun UserAction.toTransitionUserAction(): SceneTransitionUserAction {
is UserAction.Swipe ->
Swipe(
pointerCount = pointerCount,
- fromEdge =
+ fromSource =
when (this.fromEdge) {
null -> null
Edge.LEFT -> SceneTransitionEdge.Left
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
index 82d4239d7eb5..b0dc3a144533 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/EdgeDetector.kt
@@ -23,24 +23,19 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
-interface EdgeDetector {
- /**
- * Return the [Edge] associated to [position] inside a layout of size [layoutSize], given
- * [density] and [orientation].
- */
- fun edge(
- layoutSize: IntSize,
- position: IntOffset,
- density: Density,
- orientation: Orientation,
- ): Edge?
+/** The edge of a [SceneTransitionLayout]. */
+enum class Edge : SwipeSource {
+ Left,
+ Right,
+ Top,
+ Bottom,
}
val DefaultEdgeDetector = FixedSizeEdgeDetector(40.dp)
-/** An [EdgeDetector] that detects edges assuming a fixed edge size of [size]. */
-class FixedSizeEdgeDetector(val size: Dp) : EdgeDetector {
- override fun edge(
+/** An [SwipeSourceDetector] that detects edges assuming a fixed edge size of [size]. */
+class FixedSizeEdgeDetector(val size: Dp) : SwipeSourceDetector {
+ override fun source(
layoutSize: IntSize,
position: IntOffset,
density: Density,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 90f46bd4dcaa..9d4b69c51690 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -44,7 +44,7 @@ sealed class Key(val debugName: String, val identity: Any) {
class SceneKey(
name: String,
identity: Any = Object(),
-) : Key(name, identity) {
+) : Key(name, identity), UserActionResult {
@VisibleForTesting
// TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
// access internal members.
@@ -53,6 +53,10 @@ class SceneKey(
/** The unique [ElementKey] identifying this scene's root element. */
val rootElementKey = ElementKey(name, identity)
+ // Implementation of [UserActionResult].
+ override val toScene: SceneKey = this
+ override val distance: UserActionDistance? = null
+
override fun toString(): String {
return "SceneKey(debugName=$debugName)"
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 38738782c889..8552aaf0ebff 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -64,8 +64,8 @@ import androidx.compose.ui.util.fastForEach
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- enabled: Boolean,
- startDragImmediately: Boolean,
+ enabled: () -> Boolean,
+ startDragImmediately: () -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
onDragDelta: (delta: Float) -> Unit,
onDragStopped: (velocity: Float) -> Unit,
@@ -83,8 +83,8 @@ internal fun Modifier.multiPointerDraggable(
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val enabled: Boolean,
- private val startDragImmediately: Boolean,
+ private val enabled: () -> Boolean,
+ private val startDragImmediately: () -> Boolean,
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
private val onDragDelta: (Float) -> Unit,
@@ -110,10 +110,10 @@ private data class MultiPointerDraggableElement(
}
}
-private class MultiPointerDraggableNode(
+internal class MultiPointerDraggableNode(
orientation: Orientation,
- enabled: Boolean,
- var startDragImmediately: Boolean,
+ enabled: () -> Boolean,
+ var startDragImmediately: () -> Boolean,
var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit,
var onDragDelta: (Float) -> Unit,
var onDragStopped: (velocity: Float) -> Unit,
@@ -122,7 +122,7 @@ private class MultiPointerDraggableNode(
private val delegate = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))
private val velocityTracker = VelocityTracker()
- var enabled: Boolean = enabled
+ var enabled: () -> Boolean = enabled
set(value) {
// Reset the pointer input whenever enabled changed.
if (value != field) {
@@ -133,7 +133,7 @@ private class MultiPointerDraggableNode(
var orientation: Orientation = orientation
set(value) {
- // Reset the pointer input whenever enabled orientation.
+ // Reset the pointer input whenever orientation changed.
if (value != field) {
field = value
delegate.resetPointerInputHandler()
@@ -149,7 +149,7 @@ private class MultiPointerDraggableNode(
) = delegate.onPointerEvent(pointerEvent, pass, bounds)
private suspend fun PointerInputScope.pointerInput() {
- if (!enabled) {
+ if (!enabled()) {
return
}
@@ -163,8 +163,7 @@ private class MultiPointerDraggableNode(
val onDragEnd: () -> Unit = {
val maxFlingVelocity =
currentValueOf(LocalViewConfiguration).maximumFlingVelocity.let { max ->
- val maxF = max.toFloat()
- Velocity(maxF, maxF)
+ Velocity(max, max)
}
val velocity = velocityTracker.calculateVelocity(maxFlingVelocity)
@@ -183,7 +182,7 @@ private class MultiPointerDraggableNode(
detectDragGestures(
orientation = orientation,
- startDragImmediately = { startDragImmediately },
+ startDragImmediately = startDragImmediately,
onDragStart = onDragStart,
onDragEnd = onDragEnd,
onDragCancel = onDragCancel,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index f67df54b088c..af51cee2a255 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -38,7 +38,7 @@ internal class Scene(
val key: SceneKey,
layoutImpl: SceneTransitionLayoutImpl,
content: @Composable SceneScope.() -> Unit,
- actions: Map<UserAction, SceneKey>,
+ actions: Map<UserAction, UserActionResult>,
zIndex: Float,
) {
internal val scope = SceneScopeImpl(layoutImpl, this)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index ff054786cf52..aed04f688628 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -28,6 +28,8 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
@@ -75,23 +77,25 @@ internal class SceneGestureHandler(
internal var currentSource: Any? = null
- /** The [UserAction]s associated to the current swipe. */
- private var actionUpOrLeft: UserAction? = null
- private var actionDownOrRight: UserAction? = null
- private var actionUpOrLeftNoEdge: UserAction? = null
- private var actionDownOrRightNoEdge: UserAction? = null
- private var upOrLeftScene: SceneKey? = null
- private var downOrRightScene: SceneKey? = null
+ /** The [Swipes] associated to the current gesture. */
+ private var swipes: Swipes? = null
+
+ /** The [UserActionResult] associated to up and down swipes. */
+ private var upOrLeftResult: UserActionResult? = null
+ private var downOrRightResult: UserActionResult? = null
internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
if (isDrivingTransition) {
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
swipeTransition.cancelOffsetAnimation()
- updateTargetScenes(swipeTransition._fromScene)
+ updateSwipesResults(swipeTransition._fromScene)
return
}
+ check(overSlop != 0f) {
+ "onDragStarted() called while isDrivingTransition=false overSlop=0f"
+ }
val transitionState = layoutState.transitionState
if (transitionState is TransitionState.Transition) {
// TODO(b/290184746): Better handle interruptions here if state != idle.
@@ -104,18 +108,25 @@ internal class SceneGestureHandler(
}
val fromScene = layoutImpl.scene(transitionState.currentScene)
- setCurrentActions(fromScene, startedPosition, pointersDown)
+ updateSwipes(fromScene, startedPosition, pointersDown)
val (targetScene, distance) =
- findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return
-
+ findTargetSceneAndDistance(fromScene, overSlop, updateSwipesResults = true) ?: return
updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
}
- private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
- val fromEdge =
+ private fun updateSwipes(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) {
+ this.swipes = computeSwipes(fromScene, startedPosition, pointersDown)
+ }
+
+ private fun computeSwipes(
+ fromScene: Scene,
+ startedPosition: Offset?,
+ pointersDown: Int
+ ): Swipes {
+ val fromSource =
startedPosition?.let { position ->
- layoutImpl.edgeDetector.edge(
+ layoutImpl.swipeSourceDetector.source(
fromScene.targetSize,
position.round(),
layoutImpl.density,
@@ -131,7 +142,7 @@ internal class SceneGestureHandler(
Orientation.Vertical -> SwipeDirection.Up
},
pointerCount = pointersDown,
- fromEdge = fromEdge,
+ fromSource = fromSource,
)
val downOrRight =
@@ -142,33 +153,31 @@ internal class SceneGestureHandler(
Orientation.Vertical -> SwipeDirection.Down
},
pointerCount = pointersDown,
- fromEdge = fromEdge,
+ fromSource = fromSource,
)
- if (fromEdge == null) {
- actionUpOrLeft = null
- actionDownOrRight = null
- actionUpOrLeftNoEdge = upOrLeft
- actionDownOrRightNoEdge = downOrRight
+ return if (fromSource == null) {
+ Swipes(
+ upOrLeft = null,
+ downOrRight = null,
+ upOrLeftNoSource = upOrLeft,
+ downOrRightNoSource = downOrRight,
+ )
} else {
- actionUpOrLeft = upOrLeft
- actionDownOrRight = downOrRight
- actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null)
- actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null)
+ Swipes(
+ upOrLeft = upOrLeft,
+ downOrRight = downOrRight,
+ upOrLeftNoSource = upOrLeft.copy(fromSource = null),
+ downOrRightNoSource = downOrRight.copy(fromSource = null),
+ )
}
}
- /**
- * Use the layout size in the swipe orientation for swipe distance.
- *
- * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we
- * will also have to make sure that we correctly handle overscroll.
- */
- private fun Scene.getAbsoluteDistance(): Float {
- return when (orientation) {
- Orientation.Horizontal -> targetSize.width
- Orientation.Vertical -> targetSize.height
- }.toFloat()
+ private fun Scene.getAbsoluteDistance(distance: UserActionDistance?): Float {
+ val targetSize = this.targetSize
+ return with(distance ?: DefaultSwipeDistance) {
+ layoutImpl.density.absoluteDistance(targetSize, orientation)
+ }
}
internal fun onDrag(delta: Float) {
@@ -183,7 +192,7 @@ internal class SceneGestureHandler(
findTargetSceneAndDistance(
fromScene,
swipeTransition.dragOffset,
- updateScenes = isNewFromScene,
+ updateSwipesResults = isNewFromScene,
)
?: run {
onDragStopped(delta, true)
@@ -200,9 +209,31 @@ internal class SceneGestureHandler(
}
}
- private fun updateTargetScenes(fromScene: Scene) {
- upOrLeftScene = fromScene.upOrLeft()
- downOrRightScene = fromScene.downOrRight()
+ private fun updateSwipesResults(fromScene: Scene) {
+ val (upOrLeftResult, downOrRightResult) =
+ swipesResults(
+ fromScene,
+ this.swipes ?: error("updateSwipes() should be called before updateSwipesResults()")
+ )
+
+ this.upOrLeftResult = upOrLeftResult
+ this.downOrRightResult = downOrRightResult
+ }
+
+ private fun swipesResults(
+ fromScene: Scene,
+ swipes: Swipes
+ ): Pair<UserActionResult?, UserActionResult?> {
+ val userActions = fromScene.userActions
+ fun sceneToSwipePair(swipe: Swipe?): UserActionResult? {
+ return userActions[swipe ?: return null]
+ }
+
+ val upOrLeftResult =
+ sceneToSwipePair(swipes.upOrLeft) ?: sceneToSwipePair(swipes.upOrLeftNoSource)
+ val downOrRightResult =
+ sceneToSwipePair(swipes.downOrRight) ?: sceneToSwipePair(swipes.downOrRightNoSource)
+ return Pair(upOrLeftResult, downOrRightResult)
}
/**
@@ -229,9 +260,9 @@ internal class SceneGestureHandler(
// If the offset is past the distance then let's change fromScene so that the user can swipe
// to the next screen or go back to the previous one.
val offset = swipeTransition.dragOffset
- return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) {
+ return if (offset <= -absoluteDistance && upOrLeftResult?.toScene == toScene.key) {
Pair(toScene, absoluteDistance)
- } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) {
+ } else if (offset >= absoluteDistance && downOrRightResult?.toScene == toScene.key) {
Pair(toScene, -absoluteDistance)
} else {
Pair(fromScene, 0f)
@@ -244,31 +275,41 @@ internal class SceneGestureHandler(
* @param fromScene the scene from which we look for the target
* @param directionOffset signed float that indicates the direction. Positive is down or right
* negative is up or left.
- * @param updateScenes whether the target scenes should be updated to the current values held in
- * the Scenes map. Usually we don't want to update them while doing a drag, because this could
- * change the target scene (jump cutting) to a different scene, when some system state changed
- * the targets the background. However, an update is needed any time we calculate the targets
- * for a new fromScene.
+ * @param updateSwipesResults whether the target scenes should be updated to the current values
+ * held in the Scenes map. Usually we don't want to update them while doing a drag, because
+ * this could change the target scene (jump cutting) to a different scene, when some system
+ * state changed the targets the background. However, an update is needed any time we
+ * calculate the targets for a new fromScene.
* @return null when there are no targets in either direction. If one direction is null and you
* drag into the null direction this function will return the opposite direction, assuming
* that the users intention is to start the drag into the other direction eventually. If
* [directionOffset] is 0f and both direction are available, it will default to
- * [upOrLeftScene].
+ * [upOrLeftResult].
*/
private inline fun findTargetSceneAndDistance(
fromScene: Scene,
directionOffset: Float,
- updateScenes: Boolean,
+ updateSwipesResults: Boolean,
): Pair<Scene, Float>? {
- if (updateScenes) updateTargetScenes(fromScene)
- val absoluteDistance = fromScene.getAbsoluteDistance()
+ if (updateSwipesResults) updateSwipesResults(fromScene)
// Compute the target scene depending on the current offset.
return when {
- upOrLeftScene == null && downOrRightScene == null -> null
- (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null ->
- Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance)
- else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance)
+ upOrLeftResult == null && downOrRightResult == null -> null
+ (directionOffset < 0f && upOrLeftResult != null) || downOrRightResult == null ->
+ upOrLeftResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ -fromScene.getAbsoluteDistance(result.distance)
+ )
+ }
+ else ->
+ downOrRightResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ fromScene.getAbsoluteDistance(result.distance)
+ )
+ }
}
}
@@ -280,24 +321,25 @@ internal class SceneGestureHandler(
fromScene: Scene,
directionOffset: Float,
): Pair<Scene, Float>? {
- val absoluteDistance = fromScene.getAbsoluteDistance()
return when {
directionOffset > 0f ->
- upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) }
+ upOrLeftResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ -fromScene.getAbsoluteDistance(result.distance),
+ )
+ }
directionOffset < 0f ->
- downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) }
+ downOrRightResult?.let { result ->
+ Pair(
+ layoutImpl.scene(result.toScene),
+ fromScene.getAbsoluteDistance(result.distance),
+ )
+ }
else -> null
}
}
- private fun Scene.upOrLeft(): SceneKey? {
- return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge]
- }
-
- private fun Scene.downOrRight(): SceneKey? {
- return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge]
- }
-
internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) {
// The state was changed since the drag started; don't do anything.
if (!isDrivingTransition) {
@@ -515,6 +557,26 @@ internal class SceneGestureHandler(
companion object {
private const val TAG = "SceneGestureHandler"
}
+
+ private object DefaultSwipeDistance : UserActionDistance {
+ override fun Density.absoluteDistance(
+ fromSceneSize: IntSize,
+ orientation: Orientation,
+ ): Float {
+ return when (orientation) {
+ Orientation.Horizontal -> fromSceneSize.width
+ Orientation.Vertical -> fromSceneSize.height
+ }.toFloat()
+ }
+ }
+
+ /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
+ private class Swipes(
+ val upOrLeft: Swipe?,
+ val downOrRight: Swipe?,
+ val upOrLeftNoSource: Swipe?,
+ val downOrRightNoSource: Swipe?,
+ )
}
private class SceneDraggableHandler(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 80f8c1c9e987..7e0aa9c3e2b1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -27,6 +27,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
/**
* [SceneTransitionLayout] is a container that automatically animates its content whenever its state
@@ -38,7 +42,8 @@ import androidx.compose.ui.platform.LocalDensity
* UI code.
*
* @param state the state of this layout.
- * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param swipeSourceDetector the edge detector used to detect which edge a swipe is started from,
+ * if any.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param scenes the configuration of the different scenes of this layout.
@@ -48,14 +53,14 @@ import androidx.compose.ui.platform.LocalDensity
fun SceneTransitionLayout(
state: SceneTransitionLayoutState,
modifier: Modifier = Modifier,
- edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
SceneTransitionLayoutForTesting(
state,
modifier,
- edgeDetector,
+ swipeSourceDetector,
transitionInterceptionThreshold,
onLayoutImpl = null,
scenes,
@@ -76,7 +81,8 @@ fun SceneTransitionLayout(
* This is called when the user commits a transition to a new scene because of a [UserAction], for
* instance by triggering back navigation or by swiping to a new scene.
* @param transitions the definition of the transitions used to animate a change of scene.
- * @param edgeDetector the edge detector used to detect which edge a swipe is started from, if any.
+ * @param swipeSourceDetector the source detector used to detect which source a swipe is started
+ * from, if any.
* @param transitionInterceptionThreshold used during a scene transition. For the scene to be
* intercepted, the progress value must be above the threshold, and below (1 - threshold).
* @param scenes the configuration of the different scenes of this layout.
@@ -87,7 +93,7 @@ fun SceneTransitionLayout(
onChangeScene: (SceneKey) -> Unit,
transitions: SceneTransitions,
modifier: Modifier = Modifier,
- edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -95,7 +101,7 @@ fun SceneTransitionLayout(
SceneTransitionLayout(
state,
modifier,
- edgeDetector,
+ swipeSourceDetector,
transitionInterceptionThreshold,
scenes,
)
@@ -113,7 +119,7 @@ interface SceneTransitionLayoutScope {
*/
fun scene(
key: SceneKey,
- userActions: Map<UserAction, SceneKey> = emptyMap(),
+ userActions: Map<UserAction, UserActionResult> = emptyMap(),
content: @Composable SceneScope.() -> Unit,
)
}
@@ -335,7 +341,7 @@ data object Back : UserAction
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
- val fromEdge: Edge? = null,
+ val fromSource: SwipeSource? = null,
) : UserAction {
companion object {
val Left = Swipe(SwipeDirection.Left)
@@ -353,6 +359,95 @@ enum class SwipeDirection(val orientation: Orientation) {
}
/**
+ * The source of a Swipe.
+ *
+ * Important: This can be anything that can be returned by any [SwipeSourceDetector], but this must
+ * implement [equals] and [hashCode]. Note that those can be trivially implemented using data
+ * classes.
+ */
+interface SwipeSource {
+ // Require equals() and hashCode() to be implemented.
+ override fun equals(other: Any?): Boolean
+
+ override fun hashCode(): Int
+}
+
+interface SwipeSourceDetector {
+ /**
+ * Return the [SwipeSource] associated to [position] inside a layout of size [layoutSize], given
+ * [density] and [orientation].
+ */
+ fun source(
+ layoutSize: IntSize,
+ position: IntOffset,
+ density: Density,
+ orientation: Orientation,
+ ): SwipeSource?
+}
+
+/**
+ * The result of performing a [UserAction].
+ *
+ * Note: [UserActionResult] is implemented by [SceneKey], and you can also use [withDistance] to
+ * easily create a [UserActionResult] with a fixed distance:
+ * ```
+ * SceneTransitionLayout(...) {
+ * scene(
+ * Scenes.Foo,
+ * userActions =
+ * mapOf(
+ * Swipe.Right to Scene.Bar,
+ * Swipe.Down to Scene.Doe withDistance 100.dp,
+ * )
+ * )
+ * ) { ... }
+ * }
+ * ```
+ */
+interface UserActionResult {
+ /** The scene we should be transitioning to during the [UserAction]. */
+ val toScene: SceneKey
+
+ /**
+ * The distance the action takes to animate from 0% to 100%.
+ *
+ * If `null`, a default distance will be used that depends on the [UserAction] performed.
+ */
+ val distance: UserActionDistance?
+}
+
+interface UserActionDistance {
+ /**
+ * Return the **absolute** distance of the user action given the size of the scene we are
+ * animating from and the [orientation].
+ */
+ fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float
+}
+
+/**
+ * A utility function to make it possible to define user actions with a distance using the syntax
+ * `Swipe.Up to Scene.foo withDistance 100.dp`
+ */
+infix fun Pair<UserAction, SceneKey>.withDistance(
+ distance: Dp
+): Pair<UserAction, UserActionResult> {
+ val scene = second
+ val distance = FixedDistance(distance)
+ return first to
+ object : UserActionResult {
+ override val toScene: SceneKey = scene
+ override val distance: UserActionDistance = distance
+ }
+}
+
+/** The user action has a fixed [absoluteDistance]. */
+private class FixedDistance(private val distance: Dp) : UserActionDistance {
+ override fun Density.absoluteDistance(fromSceneSize: IntSize, orientation: Orientation): Float {
+ return distance.toPx()
+ }
+}
+
+/**
* An internal version of [SceneTransitionLayout] to be used for tests.
*
* Important: You should use this only in tests and if you need to access the underlying
@@ -362,7 +457,7 @@ enum class SwipeDirection(val orientation: Orientation) {
internal fun SceneTransitionLayoutForTesting(
state: SceneTransitionLayoutState,
modifier: Modifier = Modifier,
- edgeDetector: EdgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -373,7 +468,7 @@ internal fun SceneTransitionLayoutForTesting(
SceneTransitionLayoutImpl(
state = state as BaseSceneTransitionLayoutState,
density = density,
- edgeDetector = edgeDetector,
+ swipeSourceDetector = swipeSourceDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenes,
coroutineScope = coroutineScope,
@@ -394,7 +489,7 @@ internal fun SceneTransitionLayoutForTesting(
}
layoutImpl.density = density
- layoutImpl.edgeDetector = edgeDetector
+ layoutImpl.swipeSourceDetector = swipeSourceDetector
}
layoutImpl.Content(modifier)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 7cc9d2623e9c..8c5a4720e7fb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -47,7 +47,7 @@ internal typealias MovableElementContent =
internal class SceneTransitionLayoutImpl(
internal val state: BaseSceneTransitionLayoutState,
internal var density: Density,
- internal var edgeDetector: EdgeDetector,
+ internal var swipeSourceDetector: SwipeSourceDetector,
internal var transitionInterceptionThreshold: Float,
builder: SceneTransitionLayoutScope.() -> Unit,
private val coroutineScope: CoroutineScope,
@@ -140,7 +140,7 @@ internal class SceneTransitionLayoutImpl(
object : SceneTransitionLayoutScope {
override fun scene(
key: SceneKey,
- userActions: Map<UserAction, SceneKey>,
+ userActions: Map<UserAction, UserActionResult>,
content: @Composable SceneScope.() -> Unit,
) {
scenesToRemove.remove(key)
@@ -229,8 +229,10 @@ internal class SceneTransitionLayoutImpl(
// Handle back events.
// TODO(b/290184746): Make sure that this works with SystemUI once we use
// SceneTransitionLayout in Flexiglass.
- scene(state.transitionState.currentScene).userActions[Back]?.let { backScene ->
- BackHandler { with(state) { coroutineScope.onChangeScene(backScene) } }
+ scene(state.transitionState.currentScene).userActions[Back]?.let { result ->
+ // TODO(b/290184746): Handle predictive back and use result.distance if
+ // specified.
+ BackHandler { with(state) { coroutineScope.onChangeScene(result.toScene) } }
}
Box {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 0d3bc7d0cd85..b9c4ac0cd006 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -17,40 +17,98 @@
package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.PointerInputModifierNode
+import androidx.compose.ui.unit.IntSize
/**
* Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
*/
+@Stable
internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifier {
- /** Whether swipe should be enabled in the given [orientation]. */
- fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
- userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
-
- val layoutImpl = gestureHandler.layoutImpl
- val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
- val orientation = gestureHandler.orientation
- val canSwipe = currentScene.shouldEnableSwipes(orientation)
- val canOppositeSwipe =
- currentScene.shouldEnableSwipes(
- when (orientation) {
- Orientation.Vertical -> Orientation.Horizontal
- Orientation.Horizontal -> Orientation.Vertical
- }
+ return this.then(SwipeToSceneElement(gestureHandler))
+}
+
+private data class SwipeToSceneElement(
+ val gestureHandler: SceneGestureHandler,
+) : ModifierNodeElement<SwipeToSceneNode>() {
+ override fun create(): SwipeToSceneNode = SwipeToSceneNode(gestureHandler)
+
+ override fun update(node: SwipeToSceneNode) {
+ node.gestureHandler = gestureHandler
+ }
+}
+
+private class SwipeToSceneNode(
+ gestureHandler: SceneGestureHandler,
+) : DelegatingNode(), PointerInputModifierNode {
+ private val delegate =
+ delegate(
+ MultiPointerDraggableNode(
+ orientation = gestureHandler.orientation,
+ enabled = ::enabled,
+ startDragImmediately = ::startDragImmediately,
+ onDragStarted = gestureHandler.draggable::onDragStarted,
+ onDragDelta = gestureHandler.draggable::onDelta,
+ onDragStopped = gestureHandler.draggable::onDragStopped,
+ )
)
- return multiPointerDraggable(
- orientation = orientation,
- enabled = gestureHandler.isDrivingTransition || canSwipe,
- // Immediately start the drag if this our [transition] is currently animating to a scene
+ var gestureHandler: SceneGestureHandler = gestureHandler
+ set(value) {
+ if (value != field) {
+ field = value
+
+ // Make sure to update the delegate orientation. Note that this will automatically
+ // reset the underlying pointer input handler, so previous gestures will be
+ // cancelled.
+ delegate.orientation = value.orientation
+ }
+ }
+
+ override fun onPointerEvent(
+ pointerEvent: PointerEvent,
+ pass: PointerEventPass,
+ bounds: IntSize,
+ ) = delegate.onPointerEvent(pointerEvent, pass, bounds)
+
+ override fun onCancelPointerInput() = delegate.onCancelPointerInput()
+
+ private fun enabled(): Boolean {
+ return gestureHandler.isDrivingTransition ||
+ currentScene().shouldEnableSwipes(gestureHandler.orientation)
+ }
+
+ private fun currentScene(): Scene {
+ val layoutImpl = gestureHandler.layoutImpl
+ return layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
+ }
+
+ /** Whether swipe should be enabled in the given [orientation]. */
+ private fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean {
+ return userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
+ }
+
+ private fun startDragImmediately(): Boolean {
+ // Immediately start the drag if this our transition is currently animating to a scene
// (i.e. the user released their input pointer after swiping in this orientation) and the
// user can't swipe in the other direction.
- startDragImmediately =
- gestureHandler.isDrivingTransition &&
- gestureHandler.swipeTransition.isAnimatingOffset &&
- !canOppositeSwipe,
- onDragStarted = gestureHandler.draggable::onDragStarted,
- onDragDelta = gestureHandler.draggable::onDelta,
- onDragStopped = gestureHandler.draggable::onDragStopped,
- )
+ return gestureHandler.isDrivingTransition &&
+ gestureHandler.swipeTransition.isAnimatingOffset &&
+ !canOppositeSwipe()
+ }
+
+ private fun canOppositeSwipe(): Boolean {
+ val oppositeOrientation =
+ when (gestureHandler.orientation) {
+ Orientation.Vertical -> Orientation.Horizontal
+ Orientation.Horizontal -> Orientation.Vertical
+ }
+ return currentScene().shouldEnableSwipes(oppositeOrientation)
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index dc8505c43889..a764a52723af 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -320,11 +320,3 @@ interface PropertyTransformationBuilder {
anchorHeight: Boolean = true,
)
}
-
-/** The edge of a [SceneTransitionLayout]. */
-enum class Edge {
- Left,
- Right,
- Top,
- Bottom,
-}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 2841bcf4e40c..ac11d3040d67 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -22,6 +22,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
import com.android.compose.ui.util.SpaceVectorConverter
+import kotlin.math.sign
/**
* This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
@@ -117,7 +118,12 @@ class PriorityNestedScrollConnection(
return Velocity.Zero
}
- onPriorityStart(available = Offset.Zero)
+ // The offset passed to onPriorityStart() must be != 0f, so we create a small offset of 1px
+ // given the available velocity.
+ // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
+ // overscroll behavior on the Scene level.
+ val smallOffset = Offset(available.x.sign, available.y.sign)
+ onPriorityStart(available = smallOffset)
// This is the last event of a scroll gesture.
return onPriorityStop(available)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
index a68282ae78f4..cceaf57ca82b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/FixedSizeEdgeDetectorTest.kt
@@ -35,7 +35,7 @@ class FixedSizeEdgeDetectorTest {
@Test
fun horizontalEdges() {
fun horizontalEdge(position: Int): Edge? =
- detector.edge(
+ detector.source(
layoutSize,
position = IntOffset(position, 0),
density,
@@ -53,7 +53,7 @@ class FixedSizeEdgeDetectorTest {
@Test
fun verticalEdges() {
fun verticalEdge(position: Int): Edge? =
- detector.edge(
+ detector.source(
layoutSize,
position = IntOffset(0, position),
density,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 066a3e45fb3c..88363ad24d9a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -77,7 +77,7 @@ class SceneGestureHandlerTest {
userActions =
mapOf(
Swipe.Up to SceneB,
- Swipe(SwipeDirection.Up, fromEdge = Edge.Bottom) to SceneA
+ Swipe(SwipeDirection.Up, fromSource = Edge.Bottom) to SceneA
),
) {
Text("SceneC")
@@ -90,7 +90,7 @@ class SceneGestureHandlerTest {
SceneTransitionLayoutImpl(
state = layoutState,
density = Density(1f),
- edgeDetector = DefaultEdgeDetector,
+ swipeSourceDetector = DefaultEdgeDetector,
transitionInterceptionThreshold = transitionInterceptionThreshold,
builder = scenesBuilder,
coroutineScope = coroutineScope,
@@ -192,16 +192,14 @@ class SceneGestureHandlerTest {
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
assertThat(progress).isEqualTo(0.1f)
draggable.onDelta(pixels = down(0.1f))
@@ -210,10 +208,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted()
- assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(
@@ -228,10 +223,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted()
- assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(velocity = velocityThreshold)
@@ -245,7 +237,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest {
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(velocity = 0f)
@@ -256,8 +248,7 @@ class SceneGestureHandlerTest {
@Test
fun onDragReversedDirection_changeToScene() = runGestureTest {
// Drag A -> B with progress 0.6
- draggable.onDragStarted()
- draggable.onDelta(up(0.6f))
+ draggable.onDragStarted(overSlop = up(0.6f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -366,8 +357,7 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest {
// Drag A -> B with progress 0.2
- draggable.onDragStarted()
- draggable.onDelta(up(0.2f))
+ draggable.onDragStarted(overSlop = up(0.2f))
assertTransition(
currentScene = SceneA,
fromScene = SceneA,
@@ -401,9 +391,7 @@ class SceneGestureHandlerTest {
@Test
fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
- draggable.onDragStarted()
- draggable.onDelta(up(0.2f))
-
+ draggable.onDragStarted(overSlop = up(0.2f))
draggable.onDelta(up(0.2f))
draggable.onDragStopped(velocity = -velocityThreshold)
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
@@ -459,16 +447,14 @@ class SceneGestureHandlerTest {
draggable.onDragStopped(down(0.1f))
// now target changed to C for new drag that started before previous drag settled to Idle
- draggable.onDragStarted(up(0.1f))
+ draggable.onDragStarted(overSlop = 0f)
+ draggable.onDelta(up(0.1f))
assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
}
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted()
- assertTransition(currentScene = SceneA)
-
- draggable.onDelta(pixels = down(0.1f))
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
draggable.onDragStopped(
@@ -759,10 +745,8 @@ class SceneGestureHandlerTest {
@Test
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
- draggable.onDragStarted()
+ draggable.onDragStarted(overSlop = down(0.1f))
assertTransition(currentScene = SceneA)
-
- draggable.onDelta(down(0.1f))
assertThat(progress).isEqualTo(0.1f)
// now we can intercept the scroll events
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 1ec3c8ba2301..940335853221 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -17,6 +17,7 @@
package com.android.compose.animation.scene
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
@@ -89,8 +90,8 @@ class SwipeToSceneTest {
mapOf(
Swipe.Down to TestScenes.SceneA,
Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB,
- Swipe(SwipeDirection.Right, fromEdge = Edge.Left) to TestScenes.SceneB,
- Swipe(SwipeDirection.Down, fromEdge = Edge.Top) to TestScenes.SceneB,
+ Swipe(SwipeDirection.Right, fromSource = Edge.Left) to TestScenes.SceneB,
+ Swipe(SwipeDirection.Down, fromSource = Edge.Top) to TestScenes.SceneB,
),
) {
Box(Modifier.fillMaxSize())
@@ -349,4 +350,46 @@ class SwipeToSceneTest {
assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java)
assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC)
}
+
+ @Test
+ fun swipeDistance() {
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+
+ val layoutState = MutableSceneTransitionLayoutState(TestScenes.SceneA)
+ val verticalSwipeDistance = 50.dp
+ assertThat(verticalSwipeDistance).isNotEqualTo(LayoutHeight)
+
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+
+ SceneTransitionLayout(
+ state = layoutState,
+ modifier = Modifier.size(LayoutWidth, LayoutHeight)
+ ) {
+ scene(
+ TestScenes.SceneA,
+ userActions =
+ mapOf(Swipe.Down to TestScenes.SceneB withDistance verticalSwipeDistance),
+ ) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ scene(TestScenes.SceneB) { Spacer(Modifier.fillMaxSize()) }
+ }
+ }
+
+ assertThat(layoutState.currentTransition).isNull()
+
+ // Swipe by half of verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ down(middleTop)
+ moveBy(Offset(0f, touchSlop + (verticalSwipeDistance / 2).toPx()), delayMillis = 1_000)
+ }
+
+ // We should be at 50%
+ val transition = layoutState.currentTransition
+ assertThat(transition).isNotNull()
+ assertThat(transition!!.progress).isEqualTo(0.5f)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 030d41ddd8fb..c82688c2772a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -203,11 +203,17 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
featureFlags = FakeFeatureFlags()
- featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES)
+ mSetFlagsRule.enableFlags(
+ AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES,
+ )
+ mSetFlagsRule.disableFlags(
+ FLAG_SIDEFPS_CONTROLLER_REFACTOR,
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+ )
+
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
@@ -238,7 +244,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
deviceEntryInteractor = kosmos.deviceEntryInteractor
- mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
underTest =
KeyguardSecurityContainerController(
view,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
new file mode 100644
index 000000000000..9287edf4ee51
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+ private val secureSettings = FakeSettings()
+
+ private val userA11yQsShortcutsRepositoryFactory =
+ object : UserA11yQsShortcutsRepository.Factory {
+ override fun create(userId: Int): UserA11yQsShortcutsRepository {
+ return UserA11yQsShortcutsRepository(
+ userId,
+ secureSettings,
+ testScope.backgroundScope,
+ testDispatcher,
+ )
+ }
+ }
+
+ private val underTest =
+ AccessibilityQsShortcutsRepositoryImpl(userA11yQsShortcutsRepositoryFactory)
+
+ @Test
+ fun a11yQsShortcutTargetsForCorrectUsers() =
+ testScope.runTest {
+ val user0 = 0
+ val targetsForUser0 = setOf("a", "b", "c")
+ val user1 = 1
+ val targetsForUser1 = setOf("A")
+ val targetsFromUser0 by collectLastValue(underTest.a11yQsShortcutTargets(user0))
+ val targetsFromUser1 by collectLastValue(underTest.a11yQsShortcutTargets(user1))
+
+ storeA11yQsShortcutTargetsForUser(targetsForUser0, user0)
+ storeA11yQsShortcutTargetsForUser(targetsForUser1, user1)
+
+ assertThat(targetsFromUser0).isEqualTo(targetsForUser0)
+ assertThat(targetsFromUser1).isEqualTo(targetsForUser1)
+ }
+
+ private fun storeA11yQsShortcutTargetsForUser(a11yQsTargets: Set<String>, forUser: Int) {
+ secureSettings.putStringForUser(
+ SETTING_NAME,
+ a11yQsTargets.joinToString(separator = ":"),
+ forUser
+ )
+ }
+
+ companion object {
+ private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
new file mode 100644
index 000000000000..ce22e288e292
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
+ private val secureSettings = FakeSettings()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val underTest =
+ UserA11yQsShortcutsRepository(
+ USER_ID,
+ secureSettings,
+ testScope.backgroundScope,
+ testDispatcher
+ )
+
+ @Test
+ fun targetsMatchesSetting() =
+ testScope.runTest {
+ val observedTargets by collectLastValue(underTest.targets)
+ val a11yQsTargets = setOf("a", "b", "c")
+ secureSettings.putStringForUser(
+ SETTING_NAME,
+ a11yQsTargets.joinToString(SEPARATOR),
+ USER_ID
+ )
+
+ assertThat(observedTargets).isEqualTo(a11yQsTargets)
+ }
+
+ companion object {
+ private const val USER_ID = 0
+ private const val SEPARATOR = ":"
+ private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 8d6d052b8769..a862112b56e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -37,6 +37,8 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -112,6 +114,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() {
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
+ kosmos.biometricSettingsRepository,
kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
@@ -420,6 +423,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() {
@Test
fun isProlongedTouchRequiredForAuthentication_dependsOnSettingsToggle() =
testScope.runTest {
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
val isEnabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
setupFingerprint(FingerprintSensorType.POWER_BUTTON)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 81d5344ed264..bd9ca3035a07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -16,24 +16,28 @@
package com.android.systemui.communal.data.repository
+import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -44,19 +48,23 @@ import org.junit.runner.RunWith
class CommunalRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: CommunalRepositoryImpl
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ private lateinit var secureSettings: FakeSettings
+ private lateinit var userRepository: FakeUserRepository
- private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic
- private lateinit var sceneContainerRepository: SceneContainerRepository
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneContainerRepository = kosmos.sceneContainerRepository
@Before
fun setUp() {
- val kosmos = testKosmos()
- sceneContainerRepository = kosmos.sceneContainerRepository
- featureFlagsClassic = FakeFeatureFlagsClassic()
+ secureSettings = FakeSettings()
+ userRepository = kosmos.fakeUserRepository
- featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
+ val listOfUserInfo = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(listOfUserInfo)
+
+ kosmos.fakeFeatureFlagsClassic.apply { set(Flags.COMMUNAL_SERVICE_ENABLED, true) }
+ mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
underTest = createRepositoryImpl(false)
}
@@ -64,9 +72,13 @@ class CommunalRepositoryImplTest : SysuiTestCase() {
private fun createRepositoryImpl(sceneContainerEnabled: Boolean): CommunalRepositoryImpl {
return CommunalRepositoryImpl(
testScope.backgroundScope,
- featureFlagsClassic,
- FakeSceneContainerFlags(enabled = sceneContainerEnabled),
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ kosmos.fakeFeatureFlagsClassic,
+ kosmos.fakeSceneContainerFlags.apply { enabled = sceneContainerEnabled },
sceneContainerRepository,
+ kosmos.fakeUserRepository,
+ secureSettings,
)
}
@@ -147,4 +159,29 @@ class CommunalRepositoryImplTest : SysuiTestCase() {
assertThat(transitionState)
.isEqualTo(ObservableCommunalTransitionState.Idle(CommunalSceneKey.DEFAULT))
}
+
+ @Test
+ fun communalEnabledState_false_whenGlanceableHubSettingFalse() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 0, MAIN_USER_INFO.id)
+
+ val communalEnabled by collectLastValue(underTest.communalEnabledState)
+ assertThat(communalEnabled).isFalse()
+ }
+
+ @Test
+ fun communalEnabledState_true_whenGlanceableHubSettingTrue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ secureSettings.putIntForUser(GLANCEABLE_HUB_ENABLED, 1, MAIN_USER_INFO.id)
+
+ val communalEnabled by collectLastValue(underTest.communalEnabledState)
+ assertThat(communalEnabled).isTrue()
+ }
+
+ companion object {
+ private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+ private val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index bb3429e72b35..c979ca63950a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -30,7 +30,6 @@ import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
-import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.communal.widgets.widgetConfiguratorFail
import com.android.systemui.communal.widgets.widgetConfiguratorSuccess
import com.android.systemui.coroutines.collectLastValue
@@ -45,8 +44,7 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -62,24 +60,17 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
- @Mock private lateinit var appWidgetManagerOptional: Optional<AppWidgetManager>
-
@Mock private lateinit var appWidgetManager: AppWidgetManager
-
@Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
-
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
-
@Mock private lateinit var providerInfoA: AppWidgetProviderInfo
-
@Mock private lateinit var communalWidgetHost: CommunalWidgetHost
-
@Mock private lateinit var communalWidgetDao: CommunalWidgetDao
private lateinit var logBuffer: LogBuffer
+ private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>>
private val kosmos = testKosmos()
- private val testDispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
private val fakeAllowlist =
@@ -94,7 +85,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
-
+ fakeWidgets = MutableStateFlow(emptyMap())
logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
setAppWidgetIds(emptyList())
@@ -102,13 +93,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
- whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
- whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
- whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+ whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets)
underTest =
CommunalWidgetRepositoryImpl(
- appWidgetManagerOptional,
+ Optional.of(appWidgetManager),
appWidgetHost,
testScope.backgroundScope,
kosmos.testDispatcher,
@@ -119,30 +108,16 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun neverQueryDbForWidgets_whenHostIsInactive() =
- testScope.runTest {
- underTest.updateAppWidgetHostActive(false)
- underTest.communalWidgets.launchIn(testScope.backgroundScope)
- runCurrent()
-
- verify(communalWidgetDao, never()).getWidgets()
- }
-
- @Test
- fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
+ fun communalWidgets_queryWidgetsFromDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
- whenever(communalWidgetDao.getWidgets())
- .thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
+ fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry)
whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
installedProviders(listOf(stopwatchProviderInfo))
val communalWidgets by collectLastValue(underTest.communalWidgets)
- runCurrent()
verify(communalWidgetDao).getWidgets()
assertThat(communalWidgets)
.containsExactly(
@@ -157,8 +132,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_allocateId_bindWidget_andAddToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
@@ -176,8 +149,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationFails_doNotAddWidgetToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
@@ -195,23 +166,13 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.getAppWidgetInfo(id))
.thenReturn(PROVIDER_INFO_REQUIRES_CONFIGURATION)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- underTest.addWidget(
- provider,
- priority,
- object : WidgetConfigurator {
- override suspend fun configureWidget(appWidgetId: Int): Boolean {
- throw IllegalStateException("some error")
- }
- }
- )
+ underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -222,8 +183,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
@@ -241,8 +200,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun deleteWidget_removeWidgetId_andDeleteFromDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val id = 1
underTest.deleteWidget(id)
runCurrent()
@@ -254,8 +211,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun reorderWidgets_queryDb() =
testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
underTest.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
@@ -263,28 +218,6 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
- @Test
- fun appWidgetHost_startListening() =
- testScope.runTest {
- verify(appWidgetHost, never()).startListening()
-
- underTest.updateAppWidgetHostActive(true)
-
- verify(appWidgetHost).startListening()
- }
-
- @Test
- fun appWidgetHost_stopListening() =
- testScope.runTest {
- underTest.updateAppWidgetHostActive(true)
-
- verify(appWidgetHost).startListening()
-
- underTest.updateAppWidgetHostActive(false)
-
- verify(appWidgetHost).stopListening()
- }
-
private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
index e8216735fb5d..6a3fc2a060eb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
@@ -80,15 +80,4 @@ class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
assertThat(isCommunalAvailable).isFalse()
}
-
- @Test
- fun updateAppWidgetHostActive_whenStorageUnlock_false() =
- testScope.runTest {
- assertThat(widgetRepository.isHostActive()).isFalse()
-
- keyguardRepository.setIsEncryptedOrLockdown(false)
- runCurrent()
-
- assertThat(widgetRepository.isHostActive()).isFalse()
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 1b7117f41bbb..a083e7cf22c7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -172,20 +172,6 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
- fun updateAppWidgetHostActive_uponStorageUnlockAsMainUser_true() =
- testScope.runTest {
- collectLastValue(underTest.isCommunalAvailable)
- assertThat(widgetRepository.isHostActive()).isFalse()
-
- keyguardRepository.setIsEncryptedOrLockdown(false)
- userRepository.setSelectedUserInfo(mainUser)
- keyguardRepository.setKeyguardShowing(true)
- runCurrent()
-
- assertThat(widgetRepository.isHostActive()).isTrue()
- }
-
- @Test
fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() =
testScope.runTest {
// Keyguard showing, and tutorial completed.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
new file mode 100644
index 000000000000..112b0c797854
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+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.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
+
+ private lateinit var underTest: CommunalAppWidgetHostStartable
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO))
+
+ underTest =
+ CommunalAppWidgetHostStartable(
+ appWidgetHost,
+ kosmos.communalInteractor,
+ kosmos.applicationCoroutineScope,
+ kosmos.testDispatcher,
+ )
+ }
+
+ @Test
+ fun editModeShowingStartsAppWidgetHost() =
+ with(kosmos) {
+ testScope.runTest {
+ setCommunalAvailable(false)
+ communalInteractor.setEditModeOpen(true)
+ verify(appWidgetHost, never()).startListening()
+
+ underTest.start()
+ runCurrent()
+
+ verify(appWidgetHost).startListening()
+ verify(appWidgetHost, never()).stopListening()
+
+ communalInteractor.setEditModeOpen(false)
+ runCurrent()
+
+ verify(appWidgetHost).stopListening()
+ }
+ }
+
+ @Test
+ fun communalShowingStartsAppWidgetHost() =
+ with(kosmos) {
+ testScope.runTest {
+ setCommunalAvailable(true)
+ communalInteractor.setEditModeOpen(false)
+ verify(appWidgetHost, never()).startListening()
+
+ underTest.start()
+ runCurrent()
+
+ verify(appWidgetHost).startListening()
+ verify(appWidgetHost, never()).stopListening()
+
+ setCommunalAvailable(false)
+ runCurrent()
+
+ verify(appWidgetHost).stopListening()
+ }
+ }
+
+ @Test
+ fun communalAndEditModeNotShowingNeverStartListening() =
+ with(kosmos) {
+ testScope.runTest {
+ setCommunalAvailable(false)
+ communalInteractor.setEditModeOpen(false)
+
+ underTest.start()
+ runCurrent()
+
+ verify(appWidgetHost, never()).startListening()
+ verify(appWidgetHost, never()).stopListening()
+ }
+ }
+
+ private suspend fun setCommunalAvailable(available: Boolean) =
+ with(kosmos) {
+ fakeKeyguardRepository.setIsEncryptedOrLockdown(!available)
+ fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+ fakeKeyguardRepository.setKeyguardShowing(true)
+ }
+
+ private companion object {
+ val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 6a14220e6a42..6808f5d643a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId.fakeInstanceId
import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
@@ -62,7 +63,6 @@ import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.display.data.repository.display
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
@@ -194,7 +194,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
trustRepository = FakeTrustRepository()
- featureFlags = FakeFeatureFlags().apply { set(KEYGUARD_WM_STATE_REFACTOR, false) }
+ featureFlags = FakeFeatureFlags()
powerRepository = FakePowerRepository()
powerInteractor =
@@ -252,6 +252,10 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
.thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
whenever(bypassController.bypassEnabled).thenReturn(true)
underTest = createDeviceEntryFaceAuthRepositoryImpl(faceManager, bypassController)
+
+ mSetFlagsRule.disableFlags(
+ AConfigFlags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
}
private fun createDeviceEntryFaceAuthRepositoryImpl(
@@ -301,7 +305,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
faceAuthBuffer,
keyguardTransitionInteractor,
displayStateInteractor,
- featureFlags,
dumpManager,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
new file mode 100644
index 000000000000..88ad3f37dacd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AuthRippleInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val deviceEntrySourceInteractor = kosmos.deviceEntrySourceInteractor
+ private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val underTest = kosmos.authRippleInteractor
+
+ @Test
+ fun enteringDeviceFromDeviceEntryIcon_udfpsNotSupported_doesNotShowAuthRipple() =
+ testScope.runTest {
+ val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+ fingerprintPropertyRepository.supportsRearFps()
+ keyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+ assertThat(showUnlockRipple).isNull()
+ }
+
+ @Test
+ fun enteringDeviceFromDeviceEntryIcon_udfpsSupported_showsAuthRipple() =
+ testScope.runTest {
+ val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+ fingerprintPropertyRepository.supportsUdfps()
+ keyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+ assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @Test
+ fun faceUnlocked_showsAuthRipple() =
+ testScope.runTest {
+ val showUnlockRipple by collectLastValue(underTest.showUnlockRipple)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ @Test
+ fun fingerprintUnlocked_showsAuthRipple() =
+ testScope.runTest {
+ val showUnlockRippleFromBiometricUnlock by collectLastValue(underTest.showUnlockRipple)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ assertThat(showUnlockRippleFromBiometricUnlock)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
new file mode 100644
index 000000000000..d216fa0d0eff
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntrySourceInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val underTest = kosmos.deviceEntrySourceInteractor
+
+ @Test
+ fun deviceEntryFromFaceUnlock() =
+ testScope.runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+ assertThat(deviceEntryFromBiometricAuthentication)
+ .isEqualTo(BiometricUnlockSource.FACE_SENSOR)
+ }
+
+ @Test
+ fun deviceEntryFromFingerprintUnlock() = runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ runCurrent()
+ assertThat(deviceEntryFromBiometricAuthentication)
+ .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ }
+
+ @Test
+ fun noDeviceEntry() = runTest {
+ val deviceEntryFromBiometricAuthentication by
+ collectLastValue(underTest.deviceEntryFromBiometricSource)
+ keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
+ // doesn't dismiss keyguard:
+ keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.ONLY_WAKE)
+ runCurrent()
+ assertThat(deviceEntryFromBiometricAuthentication).isNull()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index dc8b97abbfe8..78ae8b119c69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -238,10 +238,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun isKeyguardUnlocked() =
+ fun isKeyguardDismissible() =
testScope.runTest {
whenever(keyguardStateController.isUnlocked).thenReturn(false)
- val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
+ val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardDismissible)
runCurrent()
assertThat(isKeyguardUnlocked).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
new file mode 100644
index 000000000000..2fe4ef78bfdc
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModelTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.content.applicationContext
+import android.hardware.biometrics.BiometricFingerprintConstants
+import android.os.PowerManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
+import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
+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.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.dozeServiceHost
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class SideFpsProgressBarViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private lateinit var underTest: SideFpsProgressBarViewModel
+ private val testScope = kosmos.testScope
+ private lateinit var mTestableLooper: TestableLooper
+
+ @Before
+ fun setup() {
+ mTestableLooper = TestableLooper.get(this)
+ allowTestableLooperAsMainThread()
+ }
+
+ private suspend fun setupRestToUnlockEnabled() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_REST_TO_UNLOCK)
+ overrideResource(R.bool.config_restToUnlockSupported, true)
+ kosmos.fakeFingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.POWER_BUTTON,
+ mutableMapOf(Pair("sensor", mock()))
+ )
+ kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0.0f,
+ transitionState = TransitionState.STARTED
+ )
+ )
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 1.0f,
+ transitionState = TransitionState.FINISHED
+ )
+ )
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+ }
+
+ @Test
+ fun whenConfigDisabled_featureIsDisabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_restToUnlockSupported, false)
+ underTest = createViewModel()
+ val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+ assertThat(enabled).isFalse()
+ }
+
+ @Test
+ fun whenConfigEnabledSensorIsPowerButtonAndSettingsToggleIsEnabled_featureIsEnabled() =
+ testScope.runTest {
+ overrideResource(R.bool.config_restToUnlockSupported, true)
+ underTest = createViewModel()
+ val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
+
+ assertThat(enabled).isFalse()
+ kosmos.fakeFingerprintPropertyRepository.setProperties(
+ 1,
+ SensorStrength.STRONG,
+ FingerprintSensorType.POWER_BUTTON,
+ mutableMapOf(Pair("sensor", mock()))
+ )
+ assertThat(enabled).isFalse()
+
+ kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
+ kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+
+ runCurrent()
+ assertThat(enabled).isTrue()
+ }
+
+ @Test
+ fun whenFingerprintAcquiredStartsWhenNotDozing_wakesUpDevice() =
+ testScope.runTest {
+ setupRestToUnlockEnabled()
+ underTest = createViewModel()
+
+ kosmos.fakeKeyguardRepository.setIsDozing(false)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+
+ runCurrent()
+
+ assertThat(kosmos.fakePowerRepository.lastWakeReason)
+ .isEqualTo(PowerManager.WAKE_REASON_BIOMETRIC)
+ }
+
+ @Test
+ fun whenFingerprintAcquiredStartsWhenDozing_pulsesAod() =
+ testScope.runTest {
+ setupRestToUnlockEnabled()
+ underTest = createViewModel()
+
+ kosmos.fakeKeyguardRepository.setIsDozing(true)
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ AcquiredFingerprintAuthenticationStatus(
+ BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
+ )
+ )
+
+ runCurrent()
+
+ verify(kosmos.dozeServiceHost).fireSideFpsAcquisitionStarted()
+ }
+
+ private fun createViewModel() =
+ SideFpsProgressBarViewModel(
+ kosmos.applicationContext,
+ kosmos.deviceEntryFingerprintAuthInteractor,
+ kosmos.sideFpsSensorInteractor,
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
+ kosmos.displayStateInteractor,
+ kosmos.testDispatcher,
+ kosmos.applicationCoroutineScope,
+ kosmos.powerInteractor,
+ )
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index eb845b2b423c..d9f24b36dac2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -35,7 +35,6 @@ import com.android.systemui.common.data.repository.fakePackageChangeRepository
import com.android.systemui.common.data.repository.packageChangeRepository
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -44,6 +43,8 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -52,6 +53,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -82,7 +84,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
underTest =
InstalledTilesComponentRepositoryImpl(
context,
- kosmos.testDispatcher,
+ testScope.backgroundScope,
kosmos.packageChangeRepository
)
}
@@ -103,6 +105,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
@@ -115,6 +118,8 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true)
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
+
assertThat(componentNames).isEmpty()
whenever(
@@ -126,6 +131,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
)
.thenReturn(listOf(resolveInfo))
kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty)
+ runCurrent()
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
@@ -146,6 +152,8 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
+
assertThat(componentNames).isEmpty()
}
@@ -165,6 +173,8 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
+
assertThat(componentNames).isEmpty()
}
@@ -210,10 +220,31 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
.thenReturn(listOf(resolveInfo))
val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId))
+ runCurrent()
assertThat(componentNames).containsExactly(TEST_COMPONENT)
}
+ @Test
+ fun loadComponentsForSameUserTwice_returnsSameFlow() =
+ testScope.runTest {
+ val flowForUser1 = underTest.getInstalledTilesComponents(1)
+ val flowForUser1TheSecondTime = underTest.getInstalledTilesComponents(1)
+ runCurrent()
+
+ assertThat(flowForUser1TheSecondTime).isEqualTo(flowForUser1)
+ }
+
+ @Test
+ fun loadComponentsForDifferentUsers_returnsDifferentFlow() =
+ testScope.runTest {
+ val flowForUser1 = underTest.getInstalledTilesComponents(1)
+ val flowForUser2 = underTest.getInstalledTilesComponents(2)
+ runCurrent()
+
+ assertThat(flowForUser2).isNotEqualTo(flowForUser1)
+ }
+
companion object {
private val INTENT = Intent(TileService.ACTION_QS_TILE)
private val FLAGS =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
new file mode 100644
index 000000000000..311122d7f8d5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.accessibility.Flags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableListTest : SysuiTestCase() {
+
+ private val factory =
+ object : A11yShortcutAutoAddable.Factory {
+ override fun create(
+ spec: TileSpec,
+ componentName: ComponentName
+ ): A11yShortcutAutoAddable {
+ return A11yShortcutAutoAddable(mock(), mock(), spec, componentName)
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() {
+ val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+ assertThat(autoAddables).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+ fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() {
+ val expected =
+ setOf(
+ factory.create(
+ TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ColorInversionTile.TILE_SPEC),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(OneHandedModeTile.TILE_SPEC),
+ AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+ AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+ ),
+ )
+
+ val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+ assertThat(autoAddables).isNotEmpty()
+ assertThat(autoAddables).containsExactlyElementsIn(expected)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
new file mode 100644
index 000000000000..3b33a43d9341
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
@@ -0,0 +1,175 @@
+/*
+ * 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.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableTest : SysuiTestCase() {
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val a11yQsShortcutsRepository = FakeAccessibilityQsShortcutsRepository()
+ private val underTest =
+ A11yShortcutAutoAddable(a11yQsShortcutsRepository, testDispatcher, SPEC, TARGET_COMPONENT)
+
+ @Test
+ fun settingNotSet_noSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+
+ assertThat(signal).isNull() // null means no emitted value
+ }
+
+ @Test
+ fun settingSetWithTarget_addSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(TARGET_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun settingSetWithoutTarget_removeSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(flow = underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(OTHER_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun settingSetWithMultipleComponents_containsTarget_addSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(OTHER_COMPONENT_FLATTEN, TARGET_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun settingSetWithMultipleComponents_doesNotContainTarget_removeSignal() =
+ testScope.runTest {
+ val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+ assertThat(signal).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(OTHER_COMPONENT_FLATTEN, OTHER_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun multipleChangesWithTarget_onlyOneAddSignal() =
+ testScope.runTest {
+ val signals by collectValues(underTest.autoAddSignal(USER_ID))
+ assertThat(signals).isEmpty()
+
+ repeat(3) {
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(TARGET_COMPONENT_FLATTEN)
+ )
+ }
+
+ assertThat(signals.size).isEqualTo(1)
+ assertThat(signals[0]).isEqualTo(AutoAddSignal.Add(SPEC))
+ }
+
+ @Test
+ fun multipleChangesWithoutTarget_onlyOneRemoveSignal() =
+ testScope.runTest {
+ val signals by collectValues(underTest.autoAddSignal(USER_ID))
+ assertThat(signals).isEmpty()
+
+ repeat(3) {
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf("$OTHER_COMPONENT_FLATTEN$it")
+ )
+ }
+
+ assertThat(signals.size).isEqualTo(1)
+ assertThat(signals[0]).isEqualTo(AutoAddSignal.Remove(SPEC))
+ }
+
+ @Test
+ fun settingSetWithTargetForUsers_onlySignalInThatUser() =
+ testScope.runTest {
+ val otherUserId = USER_ID + 1
+ val signalTargetUser by collectLastValue(underTest.autoAddSignal(USER_ID))
+ val signalOtherUser by collectLastValue(underTest.autoAddSignal(otherUserId))
+ assertThat(signalTargetUser).isNull()
+ assertThat(signalOtherUser).isNull()
+
+ a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+ USER_ID,
+ setOf(TARGET_COMPONENT_FLATTEN)
+ )
+
+ assertThat(signalTargetUser).isEqualTo(AutoAddSignal.Add(SPEC))
+ assertThat(signalOtherUser).isNull()
+ }
+
+ @Test
+ fun strategyAlways() {
+ assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+ }
+
+ companion object {
+ private val SPEC = TileSpec.create("spec")
+ private val TARGET_COMPONENT = ComponentName("FakePkgName", "FakeClassName")
+ private val TARGET_COMPONENT_FLATTEN = TARGET_COMPONENT.flattenToString()
+ private val OTHER_COMPONENT_FLATTEN =
+ ComponentName("FakePkgName", "OtherClassName").flattenToString()
+ private const val USER_ID = 0
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index f23716ccca54..d5e43f44426b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -25,10 +25,13 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
@@ -44,7 +47,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -56,7 +58,8 @@ import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
private val testDispatcher = StandardTestDispatcher()
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
@@ -79,6 +82,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
new file mode 100644
index 000000000000..51b834207cfd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeControllerSceneImplTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+
+ private lateinit var shadeInteractor: ShadeInteractor
+ private lateinit var underTest: ShadeControllerSceneImpl
+
+ @Before
+ fun setup() {
+ kosmos.testCase = this
+ kosmos.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeFeatureFlagsClassic.apply {
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ set(Flags.NSSL_DEBUG_LINES, false)
+ set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ }
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
+ testScope.runCurrent()
+ shadeInteractor = kosmos.shadeInteractor
+ underTest = kosmos.shadeControllerSceneImpl
+ }
+
+ @Test
+ fun animateCollapseShade_noForceNoExpansion() =
+ testScope.runTest {
+ // GIVEN shade is collapsed and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setDeviceEntered(true)
+ setCollapsed()
+ underTest.addPostCollapseAction(testRunnable)
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade(0, force = false, delayed = false, 1f)
+ runCurrent()
+
+ // THEN the shade remains collapsed and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ verify(testRunnable, times(1)).run()
+ }
+
+ @Test
+ fun animateCollapseShade_expandedExcludeFlagOn() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ underTest.addPostCollapseAction(testRunnable)
+ setDeviceEntered(true)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested with FLAG_EXCLUDE_NOTIFICATION_PANEL
+ underTest.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL)
+ runCurrent()
+
+ // THEN the shade remains expanded and the post-collapse action did not run
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Shade)
+ assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+ verify(testRunnable, never()).run()
+ }
+
+ @Test
+ fun animateCollapseShade_locked() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded on lockscreen
+ setDeviceEntered(false)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade()
+ runCurrent()
+
+ // THEN the shade collapses back to lockscreen and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Lockscreen)
+ }
+
+ @Test
+ fun animateCollapseShade_unlocked() =
+ testScope.runTest {
+ // GIVEN shade is fully expanded on an unlocked device
+ setDeviceEntered(true)
+ setShadeFullyExpanded()
+
+ // WHEN a collapse is requested
+ underTest.animateCollapseShade()
+ runCurrent()
+
+ // THEN the shade collapses back to lockscreen and the post-collapse action ran
+ assertThat(sceneInteractor.desiredScene.value.key).isEqualTo(SceneKey.Gone)
+ }
+
+ @Test
+ fun onCollapseShade_runPostCollapseActionsCalled() =
+ testScope.runTest {
+ // GIVEN shade is expanded and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setShadeFullyExpanded()
+ underTest.addPostCollapseAction(testRunnable)
+
+ // WHEN shade collapses
+ setCollapsed()
+
+ // THEN post-collapse action ran
+ verify(testRunnable, times(1)).run()
+ }
+
+ @Test
+ fun postOnShadeExpanded() =
+ testScope.runTest {
+ // GIVEN shade is collapsed and a post-collapse action is enqueued
+ val testRunnable = mock<Runnable>()
+ setCollapsed()
+ underTest.postOnShadeExpanded(testRunnable)
+
+ // WHEN shade expands
+ setShadeFullyExpanded()
+
+ // THEN post-collapse action ran
+ verify(testRunnable, times(1)).run()
+ }
+
+ private fun setScene(key: SceneKey) {
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+
+ private fun setDeviceEntered(isEntered: Boolean) {
+ setScene(
+ if (isEntered) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ )
+ assertThat(deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+ }
+
+ private fun setCollapsed() {
+ setScene(SceneKey.Gone)
+ assertThat(shadeInteractor.isAnyExpanded.value).isFalse()
+ }
+
+ private fun setShadeFullyExpanded() {
+ setScene(SceneKey.Shade)
+ assertThat(shadeInteractor.isAnyFullyExpanded.value).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 0a10b2c85ebe..0c7ce970cf3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -16,11 +16,10 @@
package com.android.systemui.statusbar.notification.collection.render
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -34,18 +33,19 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
@SmallTest
+@RunWith(AndroidJUnit4::class)
class GroupExpansionManagerTest : SysuiTestCase() {
- private lateinit var gem: GroupExpansionManagerImpl
+ private lateinit var underTest: GroupExpansionManagerImpl
private val dumpManager: DumpManager = mock()
private val groupMembershipManager: GroupMembershipManager = mock()
- private val featureFlags = FakeFeatureFlagsClassic()
private val pipeline: NotifPipeline = mock()
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@@ -85,79 +85,57 @@ class GroupExpansionManagerTest : SysuiTestCase() {
whenever(groupMembershipManager.getGroupSummary(summary1)).thenReturn(summary1)
whenever(groupMembershipManager.getGroupSummary(summary2)).thenReturn(summary2)
- gem = GroupExpansionManagerImpl(dumpManager, groupMembershipManager, featureFlags)
+ underTest = GroupExpansionManagerImpl(dumpManager, groupMembershipManager)
}
@Test
- fun testNotifyOnlyOnChange_enabled() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun notifyOnlyOnChange() {
var listenerCalledCount = 0
- gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
+ underTest.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
- gem.setGroupExpanded(summary1, false)
+ underTest.setGroupExpanded(summary1, false)
assertThat(listenerCalledCount).isEqualTo(0)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
assertThat(listenerCalledCount).isEqualTo(1)
- gem.setGroupExpanded(summary2, true)
- assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary2, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, false)
- assertThat(listenerCalledCount).isEqualTo(3)
- }
-
- @Test
- fun testNotifyOnlyOnChange_disabled() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
- var listenerCalledCount = 0
- gem.registerGroupExpansionChangeListener { _, _ -> listenerCalledCount++ }
-
- gem.setGroupExpanded(summary1, false)
- assertThat(listenerCalledCount).isEqualTo(1)
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
assertThat(listenerCalledCount).isEqualTo(2)
- gem.setGroupExpanded(summary2, true)
+ underTest.setGroupExpanded(summary2, false)
assertThat(listenerCalledCount).isEqualTo(3)
- gem.setGroupExpanded(summary1, true)
- assertThat(listenerCalledCount).isEqualTo(4)
- gem.setGroupExpanded(summary2, false)
- assertThat(listenerCalledCount).isEqualTo(5)
}
@Test
- fun testExpandUnattachedEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun expandUnattachedEntry() {
// First, expand the entry when it is attached.
- gem.setGroupExpanded(summary1, true)
- assertThat(gem.isGroupExpanded(summary1)).isTrue()
+ underTest.setGroupExpanded(summary1, true)
+ assertThat(underTest.isGroupExpanded(summary1)).isTrue()
// Un-attach it, and un-expand it.
NotificationEntryBuilder.setNewParent(summary1, null)
- gem.setGroupExpanded(summary1, false)
+ underTest.setGroupExpanded(summary1, false)
// Expanding again should throw.
- assertThrows(IllegalArgumentException::class.java) { gem.setGroupExpanded(summary1, true) }
+ assertThrows(IllegalArgumentException::class.java) {
+ underTest.setGroupExpanded(summary1, true)
+ }
}
@Test
- fun testSyncWithPipeline() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
- gem.attach(pipeline)
+ fun syncWithPipeline() {
+ underTest.attach(pipeline)
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
val listener: OnGroupExpansionChangeListener = mock()
- gem.registerGroupExpansionChangeListener(listener)
+ underTest.registerGroupExpansionChangeListener(listener)
beforeRenderListListener.onBeforeRenderList(entries)
verify(listener, never()).onGroupExpansionChange(any(), any())
// Expand one of the groups.
- gem.setGroupExpanded(summary1, true)
+ underTest.setGroupExpanded(summary1, true)
verify(listener).onGroupExpansionChange(summary1.row, true)
// Empty the pipeline list and verify that the group is no longer expanded.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
index c1ffa641c6a4..2cbcc5a8d925 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerTest.kt
@@ -16,67 +16,35 @@
package com.android.systemui.statusbar.notification.collection.render
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.google.common.truth.Truth.assertThat
-import org.junit.Before
import org.junit.Test
+import org.junit.runner.RunWith
@SmallTest
+@RunWith(AndroidJUnit4::class)
class GroupMembershipManagerTest : SysuiTestCase() {
- private lateinit var gmm: GroupMembershipManagerImpl
-
- private val featureFlags = FakeFeatureFlagsClassic()
-
- @Before
- fun setUp() {
- gmm = GroupMembershipManagerImpl(featureFlags)
- }
+ private var underTest = GroupMembershipManagerImpl()
@Test
- fun testIsChildInGroup_topLevel() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
+ fun isChildInGroup_topLevel() {
val topLevelEntry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.isChildInGroup(topLevelEntry)).isFalse()
- }
-
- @Test
- fun testIsChildInGroup_noParent_old() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(gmm.isChildInGroup(noParentEntry)).isTrue()
+ assertThat(underTest.isChildInGroup(topLevelEntry)).isFalse()
}
@Test
- fun testIsChildInGroup_noParent_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+ fun isChildInGroup_noParent() {
val noParentEntry = NotificationEntryBuilder().setParent(null).build()
- assertThat(gmm.isChildInGroup(noParentEntry)).isFalse()
- }
- @Test
- fun testIsChildInGroup_summary_old() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
-
- val groupKey = "group"
- val summary =
- NotificationEntryBuilder()
- .setGroup(mContext, groupKey)
- .setGroupSummary(mContext, true)
- .build()
- GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
-
- assertThat(gmm.isChildInGroup(summary)).isTrue()
+ assertThat(underTest.isChildInGroup(noParentEntry)).isFalse()
}
@Test
- fun testIsChildInGroup_summary_new() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun isChildInGroup_summary() {
val groupKey = "group"
val summary =
NotificationEntryBuilder()
@@ -85,27 +53,17 @@ class GroupMembershipManagerTest : SysuiTestCase() {
.build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
- assertThat(gmm.isChildInGroup(summary)).isFalse()
+ assertThat(underTest.isChildInGroup(summary)).isFalse()
}
@Test
- fun testIsChildInGroup_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, false)
- val childEntry = NotificationEntryBuilder().build()
- assertThat(gmm.isChildInGroup(childEntry)).isTrue()
- }
-
- @Test
- fun testIsGroupSummary_topLevelEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+ fun isGroupSummary_topLevelEntry() {
val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.isGroupSummary(entry)).isFalse()
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
}
@Test
- fun testIsGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun isGroupSummary_summary() {
val groupKey = "group"
val summary =
NotificationEntryBuilder()
@@ -114,13 +72,11 @@ class GroupMembershipManagerTest : SysuiTestCase() {
.build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
- assertThat(gmm.isGroupSummary(summary)).isTrue()
+ assertThat(underTest.isGroupSummary(summary)).isTrue()
}
@Test
- fun testIsGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun isGroupSummary_child() {
val groupKey = "group"
val summary =
NotificationEntryBuilder()
@@ -130,20 +86,17 @@ class GroupMembershipManagerTest : SysuiTestCase() {
val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
- assertThat(gmm.isGroupSummary(entry)).isFalse()
+ assertThat(underTest.isGroupSummary(entry)).isFalse()
}
@Test
- fun testGetGroupSummary_topLevelEntry() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
+ fun getGroupSummary_topLevelEntry() {
val entry = NotificationEntryBuilder().setParent(GroupEntry.ROOT_ENTRY).build()
- assertThat(gmm.getGroupSummary(entry)).isNull()
+ assertThat(underTest.getGroupSummary(entry)).isNull()
}
@Test
- fun testGetGroupSummary_summary() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun getGroupSummary_summary() {
val groupKey = "group"
val summary =
NotificationEntryBuilder()
@@ -152,13 +105,11 @@ class GroupMembershipManagerTest : SysuiTestCase() {
.build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).build()
- assertThat(gmm.getGroupSummary(summary)).isEqualTo(summary)
+ assertThat(underTest.getGroupSummary(summary)).isEqualTo(summary)
}
@Test
- fun testGetGroupSummary_child() {
- featureFlags.set(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE, true)
-
+ fun getGroupSummary_child() {
val groupKey = "group"
val summary =
NotificationEntryBuilder()
@@ -168,6 +119,6 @@ class GroupMembershipManagerTest : SysuiTestCase() {
val entry = NotificationEntryBuilder().setGroup(mContext, groupKey).build()
GroupEntryBuilder().setKey(groupKey).setSummary(summary).addChild(entry).build()
- assertThat(gmm.getGroupSummary(entry)).isEqualTo(summary)
+ assertThat(underTest.getGroupSummary(entry)).isEqualTo(summary)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
index 00a86ffc5a8f..cc4ebd4aa4c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt
@@ -1,15 +1,17 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
+ * 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.
+ * 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.phone
@@ -18,9 +20,9 @@ import android.app.PendingIntent
import android.content.Intent
import android.os.RemoteException
import android.os.UserHandle
-import android.testing.AndroidTestingRunner
import android.view.View
import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.ActivityIntentHelper
@@ -66,7 +68,7 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class ActivityStarterImplTest : SysuiTestCase() {
@Mock private lateinit var centralSurfaces: CentralSurfaces
@Mock private lateinit var assistManager: AssistManager
@@ -139,7 +141,7 @@ class ActivityStarterImplTest : SysuiTestCase() {
}
@Test
- fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLockscreen_activityLaunchAnimator() {
+ fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
val pendingIntent = mock(PendingIntent::class.java)
val parent = FrameLayout(context)
val view =
@@ -214,7 +216,7 @@ class ActivityStarterImplTest : SysuiTestCase() {
mainExecutor.runAllReady()
verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController).runPostCollapseRunnables()
+ verify(shadeController).collapseShadeForActivityStart()
}
@Test
@@ -226,7 +228,7 @@ class ActivityStarterImplTest : SysuiTestCase() {
mainExecutor.runAllReady()
verify(deviceProvisionedController).isDeviceProvisioned
- verify(shadeController, never()).runPostCollapseRunnables()
+ verify(shadeController, never()).collapseShadeForActivityStart()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
new file mode 100644
index 000000000000..b4a0a37ec9ef
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BooleanFlowOperatorsTest : SysuiTestCase() {
+
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ @Test
+ fun and_allTrue_returnsTrue() =
+ testScope.runTest {
+ val result by collectLastValue(and(TRUE, TRUE))
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun and_anyFalse_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(and(TRUE, FALSE, TRUE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun and_allFalse_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(and(FALSE, FALSE, FALSE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun or_allTrue_returnsTrue() =
+ testScope.runTest {
+ val result by collectLastValue(or(TRUE, TRUE))
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun or_anyTrue_returnsTrue() =
+ testScope.runTest {
+ val result by collectLastValue(or(FALSE, TRUE, FALSE))
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ fun or_allFalse_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(or(FALSE, FALSE, FALSE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun not_true_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(not(TRUE))
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ fun not_false_returnsFalse() =
+ testScope.runTest {
+ val result by collectLastValue(not(FALSE))
+ assertThat(result).isTrue()
+ }
+
+ private companion object {
+ val TRUE: Flow<Boolean>
+ get() = flowOf(true)
+ val FALSE: Flow<Boolean>
+ get() = flowOf(false)
+ }
+}
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index 16eba220cf5d..1365a11d7a56 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -17,6 +17,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/internet_connectivity_dialog"
android:layout_width="@dimen/large_dialog_width"
@@ -386,9 +387,8 @@
</LinearLayout>
</LinearLayout>
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/button_layout"
- android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
@@ -398,53 +398,58 @@
android:clickable="false"
android:focusable="false">
- <LinearLayout
+ <Button
+ android:id="@+id/apm_button"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="start|center_vertical"
- android:orientation="horizontal">
- <Button
- android:id="@+id/apm_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/turn_off_airplane_mode"
- android:ellipsize="end"
- android:maxLines="1"
- style="@style/Widget.Dialog.Button.BorderButton"
- android:clickable="true"
- android:focusable="true"/>
-
- <Button
- android:id="@+id/share_wifi_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/share_wifi_button_text"
- style="?android:attr/buttonBarNeutralButtonStyle"
- android:maxLines="1"
- android:ellipsize="end"
- android:clickable="true"
- android:focusable="true"
- android:visibility="gone"/>
- </LinearLayout>
-
- <LinearLayout
+ android:layout_marginEnd="10dp"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/turn_off_airplane_mode"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/share_wifi_button"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Button
+ android:id="@+id/share_wifi_button"
+ style="?android:attr/buttonBarNeutralButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:layout_gravity="end|center_vertical">
- <Button
- android:id="@+id/done_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/inline_done_button"
- style="@style/Widget.Dialog.Button"
- android:maxLines="1"
- android:ellipsize="end"
- android:clickable="true"
- android:focusable="true"/>
- </LinearLayout>
- </LinearLayout>
+ android:layout_marginEnd="10dp"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:visibility="gone"
+ app:layout_constraintHorizontal_bias="0"
+ android:text="@string/share_wifi_button_text"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/done_button"
+ app:layout_constraintStart_toEndOf="@id/apm_button"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Button
+ android:id="@+id/done_button"
+ style="@style/Widget.Dialog.Button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:ellipsize="end"
+ android:focusable="true"
+ android:maxLines="1"
+ android:text="@string/inline_done_button"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index 2c7467d726b4..fab7840a6a51 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -27,7 +27,7 @@
tools:parentTag="com.android.systemui.privacy.OngoingPrivacyChip">
>
- <LinearLayout
+ <com.android.systemui.animation.view.LaunchableLinearLayout
android:id="@+id/icons_container"
android:layout_height="@dimen/ongoing_appops_chip_height"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index dca84b9fab7c..b792acc8b097 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -27,10 +27,11 @@
android:fitsSystemWindows="true">
<!-- Placeholder for the communal UI that will be replaced if the feature is enabled. -->
- <ViewStub
+ <View
android:id="@+id/communal_ui_stub"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent"
+ android:visibility="gone" />
<com.android.systemui.scrim.ScrimView
android:id="@+id/scrim_behind"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 3839dd98cdd3..307a6192a570 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -29,6 +29,9 @@
<color name="status_bar_icons_hover_color_light">#38FFFFFF</color> <!-- 22% white -->
<color name="status_bar_icons_hover_color_dark">#38000000</color> <!-- 22% black -->
+ <!-- The dark background color behind the shade -->
+ <color name="shade_scrim_background_dark">@*android:color/black</color>
+
<!-- The color of the background in the separated list of the Global Actions menu -->
<color name="global_actions_separated_background">#F5F5F5</color>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 8ec5ccd7a080..2ab0813300e3 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -184,6 +184,7 @@
<item type="id" name="action_move_to_edge_and_hide"/>
<item type="id" name="action_move_out_edge_and_show"/>
<item type="id" name="action_remove_menu"/>
+ <item type="id" name="action_edit"/>
<!-- rounded corner view id -->
<item type="id" name="rounded_corner_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9bc7681665f1..47ac96ca7960 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1089,6 +1089,8 @@
<string name="cta_label_to_open_widget_picker">Add more widgets</string>
<!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] -->
<string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
+ <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
+ <string name="button_to_configure_widgets_text">Customize widgets</string>
<!-- Label for the button which configures widgets [CHAR LIMIT=NONE] -->
<string name="edit_widget">Edit widget</string>
<!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
@@ -2558,6 +2560,8 @@
<string name="accessibility_floating_button_action_remove_menu">Remove</string>
<!-- Action in accessibility menu to toggle on/off the accessibility feature. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_action_double_tap_to_toggle">toggle</string>
+ <!-- Action in accessibility menu to open the shortcut edit menu" [CHAR LIMIT=30]-->
+ <string name="accessibility_floating_button_action_edit">Edit</string>
<!-- Device Controls strings -->
<!-- Device Controls, Quick Settings tile title [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
index 259cca8c01e2..9e92c939dbbc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputChannelCompat.java
@@ -16,8 +16,11 @@
package com.android.systemui.shared.system;
-import android.graphics.Matrix;
+import static android.os.Trace.TRACE_TAG_INPUT;
+
import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
import android.view.InputChannel;
@@ -52,23 +55,24 @@ public class InputChannelCompat {
return target.addBatch(src);
}
- /** @see MotionEvent#createRotateMatrix */
- public static Matrix createRotationMatrix(
- /*@Surface.Rotation*/ int rotation, int displayW, int displayH) {
- return MotionEvent.createRotateMatrix(rotation, displayW, displayH);
- }
-
/**
* @see BatchedInputEventReceiver
*/
public static class InputEventReceiver {
+ private final String mName;
private final BatchedInputEventReceiver mReceiver;
+ @Deprecated
public InputEventReceiver(InputChannel inputChannel, Looper looper,
Choreographer choreographer, final InputEventListener listener) {
- mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
+ this("unknown", inputChannel, looper, choreographer, listener);
+ }
+ public InputEventReceiver(String name, InputChannel inputChannel, Looper looper,
+ Choreographer choreographer, final InputEventListener listener) {
+ mName = name;
+ mReceiver = new BatchedInputEventReceiver(inputChannel, looper, choreographer) {
@Override
public void onInputEvent(InputEvent event) {
listener.onInputEvent(event);
@@ -89,6 +93,9 @@ public class InputChannelCompat {
*/
public void dispose() {
mReceiver.dispose();
+ Trace.instant(TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver disposed");
+ Log.d(InputMonitorCompat.TAG, "Input event receiver for monitor (" + mName
+ + ") disposed");
}
}
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
index c4aac111f24c..78beaf76ea78 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
@@ -17,8 +17,13 @@ package com.android.systemui.shared.system;
import android.hardware.input.InputManagerGlobal;
import android.os.Looper;
+import android.os.Trace;
+import android.util.Log;
import android.view.Choreographer;
import android.view.InputMonitor;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
import com.android.systemui.shared.system.InputChannelCompat.InputEventListener;
import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -27,14 +32,20 @@ import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
* @see android.view.InputMonitor
*/
public class InputMonitorCompat {
+ static final String TAG = "InputMonitorCompat";
private final InputMonitor mInputMonitor;
+ private final String mName;
/**
* Monitor input on the specified display for gestures.
*/
- public InputMonitorCompat(String name, int displayId) {
+ public InputMonitorCompat(@NonNull String name, int displayId) {
+ mName = name + "-disp" + displayId;
mInputMonitor = InputManagerGlobal.getInstance()
.monitorGestureInput(name, displayId);
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " created");
+ Log.d(TAG, "Input monitor (" + mName + ") created");
+
}
/**
@@ -45,10 +56,19 @@ public class InputMonitorCompat {
}
/**
+ * @see InputMonitor#getSurface()
+ */
+ public SurfaceControl getSurface() {
+ return mInputMonitor.getSurface();
+ }
+
+ /**
* @see InputMonitor#dispose()
*/
public void dispose() {
mInputMonitor.dispose();
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " disposed");
+ Log.d(TAG, "Input monitor (" + mName + ") disposed");
}
/**
@@ -56,7 +76,9 @@ public class InputMonitorCompat {
*/
public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer,
InputEventListener listener) {
- return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer,
+ Trace.instant(Trace.TRACE_TAG_INPUT, "InputMonitorCompat-" + mName + " receiver created");
+ Log.d(TAG, "Input event receiver for monitor (" + mName + ") created");
+ return new InputEventReceiver(mName, mInputMonitor.getInputChannel(), looper, choreographer,
listener);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ecce22315c50..25d771308aea 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -19,9 +19,9 @@ package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
-import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
@@ -84,6 +84,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
@@ -100,8 +101,6 @@ import com.android.systemui.util.ViewController;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
-import dagger.Lazy;
-
import java.io.File;
import java.util.Arrays;
import java.util.Optional;
@@ -109,6 +108,7 @@ import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Provider;
+import dagger.Lazy;
import kotlinx.coroutines.Job;
/** Controller for {@link KeyguardSecurityContainer} */
@@ -330,7 +330,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
}
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mKeyguardTransitionInteractor.startDismissKeyguardTransition();
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 3e8c6a76998a..536f3afdd575 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -114,7 +114,6 @@ import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
-import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -383,7 +382,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private List<SubscriptionInfo> mSubscriptionInfo;
@VisibleForTesting
protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- private boolean mFingerprintDetectRunning;
private boolean mIsDreaming;
private boolean mLogoutEnabled;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1005,7 +1003,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean wasCancellingRestarting = mFingerprintRunningState
== BIOMETRIC_STATE_CANCELLING_RESTARTING;
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- mFingerprintDetectRunning = false;
if (wasCancellingRestarting) {
KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else {
@@ -1114,9 +1111,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
mFingerprintRunningState = fingerprintRunningState;
- if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
- mFingerprintDetectRunning = false;
- }
mLogger.logFingerprintRunningState(mFingerprintRunningState);
// Clients of KeyguardUpdateMonitor don't care about the internal state about the
// asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -2105,7 +2099,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
void resetBiometricListeningState() {
mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
- mFingerprintDetectRunning = false;
}
@VisibleForTesting
@@ -2544,10 +2537,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return;
}
final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
- final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
- final boolean runningOrRestarting = running
+ final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
|| mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
- final boolean runDetect = shouldRunFingerprintDetect();
if (runningOrRestarting && !shouldListenForFingerprint) {
if (action == BIOMETRIC_ACTION_START) {
mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2559,24 +2550,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLogger.v("Ignoring startListeningForFingerprint()");
return;
}
- startListeningForFingerprint(runDetect);
- } else if (running && runDetect && !mFingerprintDetectRunning) {
- if (action == BIOMETRIC_ACTION_STOP) {
- mLogger.v("Ignoring startListeningForFingerprint(detect)");
- return;
- }
- // stop running authentication and start running fingerprint detection
- stopListeningForFingerprint();
- startListeningForFingerprint(true);
+ startListeningForFingerprint();
}
}
- private boolean shouldRunFingerprintDetect() {
- return !isUnlockingWithFingerprintAllowed()
- || (Flags.runFingerprintDetectOnDismissibleKeyguard()
- && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
- }
-
/**
* If a user is encrypted or not.
* This is NOT related to the lock screen being visible or not.
@@ -2832,6 +2809,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& biometricEnabledForUser
&& !isUserInLockdown(user);
final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
+ final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
final boolean shouldListenBouncerState =
!strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
@@ -2894,7 +2872,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- private void startListeningForFingerprint(boolean runDetect) {
+ private void startListeningForFingerprint() {
final int userId = mSelectedUserInteractor.getSelectedUserId();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
if (mFingerprintCancelSignal != null) {
@@ -2924,20 +2902,18 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
}
- if (runDetect) {
+ if (!isUnlockingWithFingerprintAllowed()) {
mLogger.v("startListeningForFingerprint - detect");
mFpm.detectFingerprint(
mFingerprintCancelSignal,
mFingerprintDetectionCallback,
fingerprintAuthenticateOptions);
- mFingerprintDetectRunning = true;
} else {
mLogger.v("startListeningForFingerprint");
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback,
null /* handler */,
fingerprintAuthenticateOptions);
- mFingerprintDetectRunning = false;
}
setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
}
@@ -3962,7 +3938,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mSelectedUserInteractor.getSelectedUserId()));
pw.println(" getUserUnlockedWithBiometric()="
+ getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
- pw.println(" mFingerprintDetectRunning=" + mFingerprintDetectRunning);
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
pw.println(" " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index d5dc85cd8715..8e9815085e31 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -63,6 +63,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -87,6 +88,8 @@ import java.util.function.Consumer;
import javax.inject.Inject;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
/**
* Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
*
@@ -717,6 +720,7 @@ public class LockIconViewController implements Dumpable {
return mDownDetected;
}
+ @ExperimentalCoroutinesApi
@VisibleForTesting
protected void onLongPress() {
cancelTouches();
@@ -727,7 +731,8 @@ public class LockIconViewController implements Dumpable {
// pre-emptively set to true to hide view
mIsBouncerShowing = true;
- if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+ if (!DeviceEntryUdfpsRefactor.isEnabled()
+ && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
mAuthRippleController.showUnlockRipple(FINGERPRINT);
}
updateVisibility();
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 0f5f869cba5d..43728260248a 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -20,11 +20,12 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.UserInfo;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.UserHandle;
import androidx.annotation.NonNull;
-import com.android.systemui.res.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.GuestResetOrExitSessionReceiver.ResetSessionDialogFactory;
@@ -32,6 +33,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -61,6 +63,7 @@ public class GuestResumeSessionReceiver {
private final SecureSettings mSecureSettings;
private final ResetSessionDialogFactory mResetSessionDialogFactory;
private final GuestSessionNotification mGuestSessionNotification;
+ private final HandlerThread mHandlerThread;
@VisibleForTesting
public final UserTracker.Callback mUserChangedCallback =
@@ -111,13 +114,16 @@ public class GuestResumeSessionReceiver {
mSecureSettings = secureSettings;
mGuestSessionNotification = guestSessionNotification;
mResetSessionDialogFactory = resetSessionDialogFactory;
+ mHandlerThread = new HandlerThread("GuestResumeSessionReceiver");
+ mHandlerThread.start();
}
/**
* Register this receiver with the {@link BroadcastDispatcher}
*/
public void register() {
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
}
private void cancelDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 8c2d221e3f97..35f9344ae897 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepositoryImpl
import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
import com.android.systemui.accessibility.data.repository.ColorInversionRepository
@@ -31,4 +33,9 @@ interface AccessibilityModule {
@Binds
fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
+
+ @Binds
+ fun accessibilityQsShortcutsRepository(
+ impl: AccessibilityQsShortcutsRepositoryImpl
+ ): AccessibilityQsShortcutsRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
new file mode 100644
index 000000000000..401ac0f9337b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.data.repository
+
+import android.util.SparseArray
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.SharedFlow
+
+/** Provides data related to accessibility quick setting shortcut option. */
+interface AccessibilityQsShortcutsRepository {
+ /**
+ * Observable for the a11y features the user chooses in the Settings app to use the quick
+ * setting option.
+ */
+ fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>>
+}
+
+@SysUISingleton
+class AccessibilityQsShortcutsRepositoryImpl
+@Inject
+constructor(
+ private val userA11yQsShortcutsRepositoryFactory: UserA11yQsShortcutsRepository.Factory,
+) : AccessibilityQsShortcutsRepository {
+
+ @GuardedBy("userA11yQsShortcutsRepositories")
+ private val userA11yQsShortcutsRepositories = SparseArray<UserA11yQsShortcutsRepository>()
+
+ override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+ return synchronized(userA11yQsShortcutsRepositories) {
+ if (userId !in userA11yQsShortcutsRepositories) {
+ val userA11yQsShortcutsRepository =
+ userA11yQsShortcutsRepositoryFactory.create(userId)
+ userA11yQsShortcutsRepositories.put(userId, userA11yQsShortcutsRepository)
+ }
+ userA11yQsShortcutsRepositories.get(userId).targets
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
new file mode 100644
index 000000000000..ed91f03cc56e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.data.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Single user version of [AccessibilityQsShortcutsRepository]. It provides a similar interface as
+ * [TileSpecRepository], but focusing solely on the user it was created for. It observes the changes
+ * on the [Settings.Secure.ACCESSIBILITY_QS_TARGETS] for a given user
+ */
+class UserA11yQsShortcutsRepository
+@AssistedInject
+constructor(
+ @Assisted private val userId: Int,
+ private val secureSettings: SecureSettings,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+ val targets =
+ secureSettings
+ .observerFlow(userId, SETTING_NAME)
+ // Force an update
+ .onStart { emit(Unit) }
+ .map { getA11yQsShortcutTargets(userId) }
+ .flowOn(backgroundDispatcher)
+ .shareIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ replay = 1
+ )
+
+ private fun getA11yQsShortcutTargets(userId: Int): Set<String> {
+ val settingValue = secureSettings.getStringForUser(SETTING_NAME, userId) ?: ""
+ return settingValue.split(SETTING_SEPARATOR).filterNot { it.isEmpty() }.toSet()
+ }
+
+ companion object {
+ const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+ const val SETTING_SEPARATOR = ":"
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ userId: Int,
+ ): UserA11yQsShortcutsRepository
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 568b24dbd4f3..7fd72ec8ce93 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -16,127 +16,138 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.util.ArrayMap;
+import android.util.Pair;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Flags;
+import com.android.wm.shell.common.bubbles.DismissCircleView;
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import java.util.Map;
+import java.util.Objects;
+
/**
* Controls the interaction between {@link MagnetizedObject} and
* {@link MagnetizedObject.MagneticTarget}.
*/
class DragToInteractAnimationController {
- private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false;
private static final float COMPLETELY_OPAQUE = 1.0f;
private static final float COMPLETELY_TRANSPARENT = 0.0f;
private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
private static final float ANIMATING_MAX_ALPHA = 0.7f;
+ private final DragToInteractView mInteractView;
private final DismissView mDismissView;
private final MenuView mMenuView;
- private final ValueAnimator mDismissAnimator;
- private final MagnetizedObject<?> mMagnetizedObject;
- private float mMinDismissSize;
+
+ /**
+ * MagnetizedObject cannot differentiate between its MagnetizedTargets,
+ * so we need an object & an animator for every interactable.
+ */
+ private final ArrayMap<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> mInteractMap;
+
+ private float mMinInteractSize;
private float mSizePercent;
- DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
- mDismissView = dismissView;
- mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
- mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
+ DragToInteractAnimationController(DragToInteractView interactView, MenuView menuView) {
+ mDismissView = null;
+ mInteractView = interactView;
+ mInteractView.setPivotX(interactView.getWidth() / 2.0f);
+ mInteractView.setPivotY(interactView.getHeight() / 2.0f);
mMenuView = menuView;
updateResources();
- mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
- mDismissAnimator.addUpdateListener(dismissAnimation -> {
- final float animatedValue = (float) dismissAnimation.getAnimatedValue();
- final float scaleValue = Math.max(animatedValue, mSizePercent);
- dismissView.getCircle().setScaleX(scaleValue);
- dismissView.getCircle().setScaleY(scaleValue);
-
- menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ mInteractMap = new ArrayMap<>();
+ interactView.getInteractMap().forEach((viewId, pair) -> {
+ DismissCircleView circleView = pair.getFirst();
+ createMagnetizedObjectAndAnimator(circleView);
});
+ }
- mDismissAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
- super.onAnimationEnd(animation, isReverse);
+ DragToInteractAnimationController(DismissView dismissView, MenuView menuView) {
+ mDismissView = dismissView;
+ mInteractView = null;
+ mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
+ mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
+ mMenuView = menuView;
- if (isReverse) {
- mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
- mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
- mMenuView.setAlpha(COMPLETELY_OPAQUE);
- }
- }
- });
+ updateResources();
- mMagnetizedObject =
- new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView,
- new MenuAnimationController.MenuPositionProperty(
- DynamicAnimation.TRANSLATION_X),
- new MenuAnimationController.MenuPositionProperty(
- DynamicAnimation.TRANSLATION_Y)) {
- @Override
- public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
- underlyingObject.getLocationOnScreen(loc);
- }
-
- @Override
- public float getHeight(MenuView underlyingObject) {
- return underlyingObject.getHeight();
- }
-
- @Override
- public float getWidth(MenuView underlyingObject) {
- return underlyingObject.getWidth();
- }
- };
-
- final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
- dismissView.getCircle(), (int) mMinDismissSize);
- mMagnetizedObject.addTarget(magneticTarget);
- mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU);
+ mInteractMap = new ArrayMap<>();
+ createMagnetizedObjectAndAnimator(dismissView.getCircle());
}
- void showDismissView(boolean show) {
- if (show) {
- mDismissView.show();
- } else {
- mDismissView.hide();
+ void showInteractView(boolean show) {
+ if (Flags.floatingMenuDragToEdit() && mInteractView != null) {
+ if (show) {
+ mInteractView.show();
+ } else {
+ mInteractView.hide();
+ }
+ } else if (mDismissView != null) {
+ if (show) {
+ mDismissView.show();
+ } else {
+ mDismissView.hide();
+ }
}
}
void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
- mMagnetizedObject.setMagnetListener(magnetListener);
+ mInteractMap.forEach((viewId, pair) -> {
+ MagnetizedObject<?> magnetizedObject = pair.first;
+ magnetizedObject.setMagnetListener(magnetListener);
+ });
}
@VisibleForTesting
- MagnetizedObject.MagnetListener getMagnetListener() {
- return mMagnetizedObject.getMagnetListener();
+ MagnetizedObject.MagnetListener getMagnetListener(int id) {
+ return Objects.requireNonNull(mInteractMap.get(id)).first.getMagnetListener();
}
void maybeConsumeDownMotionEvent(MotionEvent event) {
- mMagnetizedObject.maybeConsumeMotionEvent(event);
+ mInteractMap.forEach((viewId, pair) -> {
+ MagnetizedObject<?> magnetizedObject = pair.first;
+ magnetizedObject.maybeConsumeMotionEvent(event);
+ });
+ }
+
+ private int maybeConsumeMotionEvent(MotionEvent event) {
+ for (Map.Entry<Integer, Pair<MagnetizedObject<MenuView>, ValueAnimator>> set:
+ mInteractMap.entrySet()) {
+ MagnetizedObject<MenuView> magnetizedObject = set.getValue().first;
+ if (magnetizedObject.maybeConsumeMotionEvent(event)) {
+ return set.getKey();
+ }
+ }
+ return empty;
}
/**
- * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
- * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
+ * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized objects
+ * to check if it was within a magnetic field.
+ * It should be used in the {@link MenuListViewTouchHandler}.
*
* @param event that move the magnetized object which is also the menu list view.
- * @return true if the location of the motion events moves within the magnetic field of a
- * target, but false if didn't set
+ * @return id of a target if the location of the motion events moves
+ * within the field of the target, otherwise it returns{@link android.R.id#empty}.
+ * <p>
* {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
- boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
- return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ int maybeConsumeMoveMotionEvent(MotionEvent event) {
+ return maybeConsumeMotionEvent(event);
}
/**
@@ -144,31 +155,93 @@ class DragToInteractAnimationController {
* within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
*
* @param event that move the magnetized object which is also the menu list view.
- * @return true if the location of the motion events moves within the magnetic field of a
- * target, but false if didn't set
+ * @return id of a target if the location of the motion events moves
+ * within the field of the target, otherwise it returns{@link android.R.id#empty}.
* {@link DragToInteractAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
*/
- boolean maybeConsumeUpMotionEvent(MotionEvent event) {
- return mMagnetizedObject.maybeConsumeMotionEvent(event);
+ int maybeConsumeUpMotionEvent(MotionEvent event) {
+ return maybeConsumeMotionEvent(event);
}
- void animateDismissMenu(boolean scaleUp) {
+ void animateInteractMenu(int targetViewId, boolean scaleUp) {
+ Pair<MagnetizedObject<MenuView>, ValueAnimator> value = mInteractMap.get(targetViewId);
+ if (value == null) {
+ return;
+ }
+ ValueAnimator animator = value.second;
if (scaleUp) {
- mDismissAnimator.start();
+ animator.start();
} else {
- mDismissAnimator.reverse();
+ animator.reverse();
}
}
void updateResources() {
- final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ final float maxInteractSize = mMenuView.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.dismiss_circle_size);
- mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
+ mMinInteractSize = mMenuView.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.dismiss_circle_small);
- mSizePercent = mMinDismissSize / maxDismissSize;
+ mSizePercent = mMinInteractSize / maxInteractSize;
}
- interface DismissCallback {
- void onDismiss();
+ /**
+ * Creates a magnetizedObject & valueAnimator pair for the provided circleView,
+ * and adds them to the interactMap.
+ *
+ * @param circleView circleView to create objects for.
+ */
+ private void createMagnetizedObjectAndAnimator(DismissCircleView circleView) {
+ MagnetizedObject<MenuView> magnetizedObject = new MagnetizedObject<MenuView>(
+ mMenuView.getContext(), mMenuView,
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_X),
+ new MenuAnimationController.MenuPositionProperty(
+ DynamicAnimation.TRANSLATION_Y)) {
+ @Override
+ public void getLocationOnScreen(MenuView underlyingObject, @NonNull int[] loc) {
+ underlyingObject.getLocationOnScreen(loc);
+ }
+
+ @Override
+ public float getHeight(MenuView underlyingObject) {
+ return underlyingObject.getHeight();
+ }
+
+ @Override
+ public float getWidth(MenuView underlyingObject) {
+ return underlyingObject.getWidth();
+ }
+ };
+ // Avoid unintended selection of an object / option
+ magnetizedObject.setFlingToTargetEnabled(false);
+ magnetizedObject.addTarget(new MagnetizedObject.MagneticTarget(
+ circleView, (int) mMinInteractSize));
+
+ final ValueAnimator animator =
+ ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
+
+ animator.addUpdateListener(dismissAnimation -> {
+ final float animatedValue = (float) dismissAnimation.getAnimatedValue();
+ final float scaleValue = Math.max(animatedValue, mSizePercent);
+ circleView.setScaleX(scaleValue);
+ circleView.setScaleY(scaleValue);
+
+ mMenuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
+ });
+
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+ super.onAnimationEnd(animation, isReverse);
+
+ if (isReverse) {
+ circleView.setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
+ circleView.setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
+ mMenuView.setAlpha(COMPLETELY_OPAQUE);
+ }
+ }
+ });
+
+ mInteractMap.put(circleView.getId(), new Pair<>(magnetizedObject, animator));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
new file mode 100644
index 000000000000..0ef3d200d1fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.floatingmenu
+
+import android.animation.ObjectAnimator
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.GradientDrawable
+import android.util.ArrayMap
+import android.util.IntProperty
+import android.util.Log
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.Space
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.core.content.ContextCompat
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
+import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.common.bubbles.DismissCircleView
+import com.android.wm.shell.common.bubbles.DismissView
+
+/**
+ * View that handles interactions between DismissCircleView and BubbleStackView.
+ *
+ * @note [setup] method should be called after initialisation
+ */
+class DragToInteractView(context: Context) : FrameLayout(context) {
+ /**
+ * The configuration is used to provide module specific resource ids
+ *
+ * @see [setup] method
+ */
+ data class Config(
+ /** dimen resource id of the dismiss target circle view size */
+ @DimenRes val targetSizeResId: Int,
+ /** dimen resource id of the icon size in the dismiss target */
+ @DimenRes val iconSizeResId: Int,
+ /** dimen resource id of the bottom margin for the dismiss target */
+ @DimenRes var bottomMarginResId: Int,
+ /** dimen resource id of the height for dismiss area gradient */
+ @DimenRes val floatingGradientHeightResId: Int,
+ /** color resource id of the dismiss area gradient color */
+ @ColorRes val floatingGradientColorResId: Int,
+ /** drawable resource id of the dismiss target background */
+ @DrawableRes val backgroundResId: Int,
+ /** drawable resource id of the icon for the dismiss target */
+ @DrawableRes val iconResId: Int
+ )
+
+ companion object {
+ private const val SHOULD_SETUP = "The view isn't ready. Should be called after `setup`"
+ private val TAG = DragToInteractView::class.simpleName
+ }
+
+ // START DragToInteractView modification
+ // We could technically access each DismissCircleView from their Animator,
+ // but the animators only store a weak reference to their targets. This is safer.
+ var interactMap = ArrayMap<Int, Pair<DismissCircleView, PhysicsAnimator<DismissCircleView>>>()
+ // END DragToInteractView modification
+ var isShowing = false
+ var config: Config? = null
+
+ private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
+ private val INTERACT_SCRIM_FADE_MS = 200L
+ private var wm: WindowManager =
+ context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private var gradientDrawable: GradientDrawable? = null
+
+ private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
+ object : IntProperty<GradientDrawable>("alpha") {
+ override fun setValue(d: GradientDrawable, percent: Int) {
+ d.alpha = percent
+ }
+ override fun get(d: GradientDrawable): Int {
+ return d.alpha
+ }
+ }
+
+ init {
+ clipToPadding = false
+ clipChildren = false
+ visibility = View.INVISIBLE
+
+ // START DragToInteractView modification
+ // Resources included within implementation as we aren't concerned with decoupling them.
+ setup(
+ Config(
+ targetSizeResId = R.dimen.dismiss_circle_size,
+ iconSizeResId = R.dimen.dismiss_target_x_size,
+ bottomMarginResId = R.dimen.floating_dismiss_bottom_margin,
+ floatingGradientHeightResId = R.dimen.floating_dismiss_gradient_height,
+ floatingGradientColorResId = android.R.color.system_neutral1_900,
+ backgroundResId = R.drawable.dismiss_circle_background,
+ iconResId = R.drawable.pip_ic_close_white
+ )
+ )
+ // END DragToInteractView modification
+ }
+
+ /**
+ * Sets up view with the provided resource ids.
+ *
+ * Decouples resource dependency in order to be used externally (e.g. Launcher). Usually called
+ * with default params in module specific extension:
+ *
+ * @see [DismissView.setup] in DismissViewExt.kt
+ */
+ fun setup(config: Config) {
+ this.config = config
+
+ // Setup layout
+ layoutParams =
+ LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ resources.getDimensionPixelSize(config.floatingGradientHeightResId),
+ Gravity.BOTTOM
+ )
+ updatePadding()
+
+ // Setup gradient
+ gradientDrawable = createGradient(color = config.floatingGradientColorResId)
+ background = gradientDrawable
+
+ // START DragToInteractView modification
+
+ // Setup LinearLayout. Added to organize multiple circles.
+ val linearLayout = LinearLayout(context)
+ linearLayout.layoutParams =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ linearLayout.weightSum = 0f
+ addView(linearLayout)
+
+ // Setup DismissCircleView. Code block replaced with repeatable functions
+ addSpace(linearLayout)
+ addCircle(
+ config,
+ com.android.systemui.res.R.id.action_remove_menu,
+ R.drawable.pip_ic_close_white,
+ linearLayout
+ )
+ addCircle(
+ config,
+ com.android.systemui.res.R.id.action_edit,
+ com.android.systemui.res.R.drawable.ic_screenshot_edit,
+ linearLayout
+ )
+ // END DragToInteractView modification
+ }
+
+ /** Animates this view in. */
+ fun show() {
+ if (isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
+ isShowing = true
+ visibility = View.VISIBLE
+ val alphaAnim =
+ ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 255)
+ alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+ alphaAnim.start()
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator.cancel()
+ animator.spring(DynamicAnimation.TRANSLATION_Y, 0f, spring).start()
+ }
+ // END DragToInteractView modification
+ }
+
+ /**
+ * Animates this view out, as well as the circle that encircles the bubbles, if they were
+ * dragged into the target and encircled.
+ */
+ fun hide() {
+ if (!isShowing) return
+ val gradientDrawable = checkExists(gradientDrawable) ?: return
+ isShowing = false
+ val alphaAnim =
+ ObjectAnimator.ofInt(gradientDrawable, GRADIENT_ALPHA, gradientDrawable.alpha, 0)
+ alphaAnim.duration = INTERACT_SCRIM_FADE_MS
+ alphaAnim.start()
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator
+ .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), spring)
+ .withEndActions({ visibility = View.INVISIBLE })
+ .start()
+ }
+ // END DragToInteractView modification
+ }
+
+ /** Cancels the animator for the dismiss target. */
+ fun cancelAnimators() {
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val animator = it.value.second
+ animator.cancel()
+ }
+ // END DragToInteractView modification
+ }
+
+ fun updateResources() {
+ val config = checkExists(config) ?: return
+ updatePadding()
+ layoutParams.height = resources.getDimensionPixelSize(config.floatingGradientHeightResId)
+ val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+
+ // START DragToInteractView modification
+ interactMap.forEach {
+ val circle = it.value.first
+ circle.layoutParams.width = targetSize
+ circle.layoutParams.height = targetSize
+ circle.requestLayout()
+ }
+ // END DragToInteractView modification
+ }
+
+ private fun createGradient(@ColorRes color: Int): GradientDrawable {
+ val gradientColor = ContextCompat.getColor(context, color)
+ val alpha = 0.7f * 255
+ val gradientColorWithAlpha =
+ Color.argb(
+ alpha.toInt(),
+ Color.red(gradientColor),
+ Color.green(gradientColor),
+ Color.blue(gradientColor)
+ )
+ val gd =
+ GradientDrawable(
+ GradientDrawable.Orientation.BOTTOM_TOP,
+ intArrayOf(gradientColorWithAlpha, Color.TRANSPARENT)
+ )
+ gd.setDither(true)
+ gd.alpha = 0
+ return gd
+ }
+
+ private fun updatePadding() {
+ val config = checkExists(config) ?: return
+ val insets: WindowInsets = wm.currentWindowMetrics.windowInsets
+ val navInset = insets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars())
+ setPadding(
+ 0,
+ 0,
+ 0,
+ navInset.bottom + resources.getDimensionPixelSize(config.bottomMarginResId)
+ )
+ }
+
+ /**
+ * Checks if the value is set up and exists, if not logs an exception. Used for convenient
+ * logging in case `setup` wasn't called before
+ *
+ * @return value provided as argument
+ */
+ private fun <T> checkExists(value: T?): T? {
+ if (value == null) Log.e(TAG, SHOULD_SETUP)
+ return value
+ }
+
+ // START DragToInteractView modification
+ private fun addSpace(parent: LinearLayout) {
+ val space = Space(context)
+ // Spaces are weighted equally to space out circles evenly
+ space.layoutParams =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ 1f
+ )
+ parent.addView(space)
+ parent.weightSum = parent.weightSum + 1f
+ }
+
+ private fun addCircle(config: Config, id: Int, iconResId: Int, parent: LinearLayout) {
+ val targetSize = resources.getDimensionPixelSize(config.targetSizeResId)
+ val circleLayoutParams = LinearLayout.LayoutParams(targetSize, targetSize, 0f)
+ circleLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ val circle = DismissCircleView(context)
+ circle.id = id
+ circle.setup(config.backgroundResId, iconResId, config.iconSizeResId)
+ circle.layoutParams = circleLayoutParams
+
+ // Initial position with circle offscreen so it's animated up
+ circle.translationY =
+ resources.getDimensionPixelSize(config.floatingGradientHeightResId).toFloat()
+
+ interactMap[circle.id] = Pair(circle, PhysicsAnimator.getInstance(circle))
+ parent.addView(circle)
+ addSpace(parent)
+ }
+ // END DragToInteractView modification
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index a2705584d76a..d3e85e092b3a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -37,7 +37,6 @@ import androidx.dynamicanimation.animation.SpringForce;
import androidx.recyclerview.widget.RecyclerView;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;
import java.util.HashMap;
@@ -73,7 +72,6 @@ class MenuAnimationController {
private final ValueAnimator mFadeOutAnimator;
private final Handler mHandler;
private boolean mIsFadeEffectEnabled;
- private DragToInteractAnimationController.DismissCallback mDismissCallback;
private Runnable mSpringAnimationsEndAction;
// Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
@@ -170,11 +168,6 @@ class MenuAnimationController {
mSpringAnimationsEndAction = runnable;
}
- void setDismissCallback(
- DragToInteractAnimationController.DismissCallback dismissCallback) {
- mDismissCallback = dismissCallback;
- }
-
void moveToTopLeftPosition() {
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
@@ -205,13 +198,6 @@ class MenuAnimationController {
constrainPositionAndUpdate(position, /* writeToPosition = */ true);
}
- void removeMenu() {
- Preconditions.checkArgument(mDismissCallback != null,
- "The dismiss callback should be initialized first.");
-
- mDismissCallback.onDismiss();
- }
-
void flingMenuThenSpringToEdge(float x, float velocityX, float velocityY) {
final boolean shouldMenuFlingLeft = isOnLeftSide()
? velocityX < ESCAPE_VELOCITY
@@ -334,8 +320,6 @@ class MenuAnimationController {
moveToEdgeAndHide();
return true;
}
-
- fadeOutIfEnabled();
return false;
}
@@ -453,8 +437,6 @@ class MenuAnimationController {
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
constrainPositionAndUpdate(position, writeToPosition);
- fadeOutIfEnabled();
-
if (mSpringAnimationsEndAction != null) {
mSpringAnimationsEndAction.run();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
index 9c22a7738ad6..975a6020430d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegate.java
@@ -27,6 +27,7 @@ import androidx.annotation.NonNull;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
+import com.android.systemui.Flags;
import com.android.systemui.res.R;
/**
@@ -35,15 +36,18 @@ import com.android.systemui.res.R;
*/
class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.ItemDelegate {
private final MenuAnimationController mAnimationController;
+ private final MenuViewLayer mMenuViewLayer;
MenuItemAccessibilityDelegate(@NonNull RecyclerViewAccessibilityDelegate recyclerViewDelegate,
- MenuAnimationController animationController) {
+ MenuAnimationController animationController, MenuViewLayer menuViewLayer) {
super(recyclerViewDelegate);
mAnimationController = animationController;
+ mMenuViewLayer = menuViewLayer;
}
@Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ public void onInitializeAccessibilityNodeInfo(
+ @NonNull View host, @NonNull AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final Resources res = host.getResources();
@@ -90,6 +94,15 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It
R.id.action_remove_menu,
res.getString(R.string.accessibility_floating_button_action_remove_menu));
info.addAction(removeMenu);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ final AccessibilityNodeInfoCompat.AccessibilityActionCompat edit =
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ R.id.action_edit,
+ res.getString(
+ R.string.accessibility_floating_button_action_remove_menu));
+ info.addAction(edit);
+ }
}
@Override
@@ -132,8 +145,8 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It
return true;
}
- if (action == R.id.action_remove_menu) {
- mAnimationController.removeMenu();
+ if (action == R.id.action_remove_menu || action == R.id.action_edit) {
+ mMenuViewLayer.dispatchAccessibilityAction(action);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index 52e7b91d373e..75191685b119 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
+
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -78,10 +80,9 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuAnimationController.onDraggingStart();
}
- mDragToInteractAnimationController.showDismissView(/* show= */ true);
-
- if (!mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(
- motionEvent)) {
+ mDragToInteractAnimationController.showInteractView(/* show= */ true);
+ if (mDragToInteractAnimationController.maybeConsumeMoveMotionEvent(motionEvent)
+ == empty) {
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
mMenuAnimationController.moveToPositionYIfNeeded(
mMenuTranslationDown.y + dy);
@@ -94,21 +95,19 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
final float endX = mMenuTranslationDown.x + dx;
mIsDragging = false;
+ mDragToInteractAnimationController.showInteractView(/* show= */ false);
+
if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
- mDragToInteractAnimationController.showDismissView(/* show= */ false);
mMenuAnimationController.fadeOutIfEnabled();
-
return true;
}
- if (!mDragToInteractAnimationController.maybeConsumeUpMotionEvent(
- motionEvent)) {
+ if (mDragToInteractAnimationController.maybeConsumeUpMotionEvent(motionEvent)
+ == empty) {
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
- mDragToInteractAnimationController.showDismissView(/* show= */ false);
}
-
// Avoid triggering the listener of the item.
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 76808cb7ab7b..334cc87144f9 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -21,24 +21,28 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.annotation.SuppressLint;
import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.SettingsStringUtil;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
-import androidx.core.view.AccessibilityDelegateCompat;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.Flags;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.Collections;
@@ -72,26 +76,20 @@ class MenuView extends FrameLayout implements
private final MenuAnimationController mMenuAnimationController;
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
private OnMoveToTuckedListener mMoveToTuckedListener;
+ private SecureSettings mSecureSettings;
- MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
+ MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance,
+ SecureSettings secureSettings) {
super(context);
mMenuViewModel = menuViewModel;
mMenuViewAppearance = menuViewAppearance;
+ mSecureSettings = secureSettings;
mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance);
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context));
- mTargetFeaturesView.setAccessibilityDelegateCompat(
- new RecyclerViewAccessibilityDelegate(mTargetFeaturesView) {
- @NonNull
- @Override
- public AccessibilityDelegateCompat getItemDelegate() {
- return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
- mMenuAnimationController);
- }
- });
setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
// Avoid drawing out of bounds of the parent view
setClipToOutline(true);
@@ -278,6 +276,7 @@ class MenuView extends FrameLayout implements
if (mFeaturesChangeListener != null) {
mFeaturesChangeListener.onChange(newTargetFeatures);
}
+
mMenuAnimationController.fadeOutIfEnabled();
}
@@ -306,6 +305,10 @@ class MenuView extends FrameLayout implements
return mMenuViewAppearance.getMenuPosition();
}
+ RecyclerView getTargetFeaturesView() {
+ return mTargetFeaturesView;
+ }
+
void persistPositionAndUpdateEdge(Position percentagePosition) {
mMenuViewModel.updateMenuSavingPosition(percentagePosition);
mMenuViewAppearance.setPercentagePosition(percentagePosition);
@@ -424,6 +427,35 @@ class MenuView extends FrameLayout implements
onPositionChanged();
}
+ void gotoEditScreen() {
+ if (!Flags.floatingMenuDragToEdit()) {
+ return;
+ }
+ mMenuAnimationController.flingMenuThenSpringToEdge(
+ getMenuPosition().x, 100f, 0f);
+ mContext.startActivity(getIntentForEditScreen());
+ }
+
+ Intent getIntentForEditScreen() {
+ List<String> targets = new SettingsStringUtil.ColonDelimitedSet.OfStrings(
+ mSecureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).stream().toList();
+
+ Intent intent = new Intent(
+ Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ Bundle args = new Bundle();
+ Bundle fragmentArgs = new Bundle();
+ fragmentArgs.putStringArray("targets", targets.toArray(new String[0]));
+ args.putBundle(":settings:show_fragment_args", fragmentArgs);
+ // TODO: b/318748373 - The fragment should set its own title using the targets
+ args.putString(
+ ":settings:show_fragment_title", "Accessibility Shortcut");
+ intent.replaceExtras(args);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return intent;
+ }
+
private InstantInsetLayerDrawable getContainerViewInsetLayer() {
return (InstantInsetLayerDrawable) getBackground();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 97999cc19dc8..bb5364d798da 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -59,7 +59,10 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.core.view.AccessibilityDelegateCompat;
import androidx.lifecycle.Observer;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,6 +97,8 @@ class MenuViewLayer extends FrameLayout implements
private final MenuListViewTouchHandler mMenuListViewTouchHandler;
private final MenuMessageView mMessageView;
private final DismissView mDismissView;
+ private final DragToInteractView mDragToInteractView;
+
private final MenuViewAppearance mMenuViewAppearance;
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
@@ -178,7 +183,10 @@ class MenuViewLayer extends FrameLayout implements
};
MenuViewLayer(@NonNull Context context, WindowManager windowManager,
- AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu,
+ AccessibilityManager accessibilityManager,
+ MenuViewModel menuViewModel,
+ MenuViewAppearance menuViewAppearance, MenuView menuView,
+ IAccessibilityFloatingMenu floatingMenu,
SecureSettings secureSettings) {
super(context);
@@ -190,43 +198,52 @@ class MenuViewLayer extends FrameLayout implements
mFloatingMenu = floatingMenu;
mSecureSettings = secureSettings;
- mMenuViewModel = new MenuViewModel(context, accessibilityManager, secureSettings);
- mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
- mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
+ mMenuViewModel = menuViewModel;
+ mMenuViewAppearance = menuViewAppearance;
+ mMenuView = menuView;
+ RecyclerView targetFeaturesView = mMenuView.getTargetFeaturesView();
+ targetFeaturesView.setAccessibilityDelegateCompat(
+ new RecyclerViewAccessibilityDelegate(targetFeaturesView) {
+ @NonNull
+ @Override
+ public AccessibilityDelegateCompat getItemDelegate() {
+ return new MenuItemAccessibilityDelegate(/* recyclerViewDelegate= */ this,
+ mMenuAnimationController, MenuViewLayer.this);
+ }
+ });
mMenuAnimationController = mMenuView.getMenuAnimationController();
- if (Flags.floatingMenuDragToHide()) {
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification);
- } else {
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
- }
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
+ mDragToInteractView = new DragToInteractView(context);
DismissViewUtils.setup(mDismissView);
+ mDismissView.getCircle().setId(R.id.action_remove_menu);
mNotificationFactory = new MenuNotificationFactory(context);
mNotificationManager = context.getSystemService(NotificationManager.class);
- mDragToInteractAnimationController = new DragToInteractAnimationController(
- mDismissView, mMenuView);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDragToInteractView, mMenuView);
+ } else {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, mMenuView);
+ }
mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ true);
+ mDragToInteractAnimationController.animateInteractMenu(
+ target.getTargetView().getId(), /* scaleUp= */ true);
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velocityX, float velocityY, boolean wasFlungOut) {
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ mDragToInteractAnimationController.animateInteractMenu(
+ target.getTargetView().getId(), /* scaleUp= */ false);
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (Flags.floatingMenuDragToHide()) {
- hideMenuAndShowNotification();
- } else {
- hideMenuAndShowMessage();
- }
- mDismissView.hide();
- mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
+ dispatchAccessibilityAction(target.getTargetView().getId());
}
});
@@ -262,7 +279,11 @@ class MenuViewLayer extends FrameLayout implements
});
addView(mMenuView, LayerIndex.MENU_VIEW);
- addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ if (Flags.floatingMenuDragToEdit()) {
+ addView(mDragToInteractView, LayerIndex.DISMISS_VIEW);
+ } else {
+ addView(mDismissView, LayerIndex.DISMISS_VIEW);
+ }
addView(mMessageView, LayerIndex.MESSAGE_VIEW);
if (Flags.floatingMenuAnimatedTuck()) {
@@ -272,6 +293,7 @@ class MenuViewLayer extends FrameLayout implements
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ mDragToInteractView.updateResources();
mDismissView.updateResources();
mDragToInteractAnimationController.updateResources();
}
@@ -428,6 +450,23 @@ class MenuViewLayer extends FrameLayout implements
}
}
+ void dispatchAccessibilityAction(int id) {
+ if (id == R.id.action_remove_menu) {
+ if (Flags.floatingMenuDragToHide()) {
+ hideMenuAndShowNotification();
+ } else {
+ hideMenuAndShowMessage();
+ }
+ } else if (id == R.id.action_edit
+ && Flags.floatingMenuDragToEdit()) {
+ mMenuView.gotoEditScreen();
+ }
+ mDismissView.hide();
+ mDragToInteractView.hide();
+ mDragToInteractAnimationController.animateInteractMenu(
+ id, /* scaleUp= */ false);
+ }
+
private CharSequence getMigrationMessage() {
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -475,7 +514,8 @@ class MenuViewLayer extends FrameLayout implements
mEduTooltipView = Optional.empty();
}
- private void hideMenuAndShowMessage() {
+ @VisibleForTesting
+ void hideMenuAndShowMessage() {
final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
SHOW_MESSAGE_DELAY_MS,
AccessibilityManager.FLAG_CONTENT_TEXT
@@ -485,7 +525,8 @@ class MenuViewLayer extends FrameLayout implements
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
}
- private void hideMenuAndShowNotification() {
+ @VisibleForTesting
+ void hideMenuAndShowNotification() {
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
showNotification();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 1f549525256b..bc9d1ffd259b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -39,7 +39,16 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
MenuViewLayerController(Context context, WindowManager windowManager,
AccessibilityManager accessibilityManager, SecureSettings secureSettings) {
mWindowManager = windowManager;
- mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this,
+
+ MenuViewModel menuViewModel = new MenuViewModel(
+ context, accessibilityManager, secureSettings);
+ MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context, windowManager);
+
+ mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager,
+ menuViewModel,
+ menuViewAppearance,
+ new MenuView(context, menuViewModel, menuViewAppearance, secureSettings),
+ this,
secureSettings);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 86f372a94848..d2c62272e2ec 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -25,6 +25,7 @@ import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricSourceType
import android.util.DisplayMetrics
import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -35,8 +36,11 @@ import com.android.systemui.Flags.lightRevealMigration
import com.android.systemui.biometrics.data.repository.FacePropertyRepository
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
@@ -51,7 +55,6 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.ViewController
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Provider
@@ -64,7 +67,6 @@ import javax.inject.Provider
*
* The ripple uses the accent color of the current theme.
*/
-@ExperimentalCoroutinesApi
@SysUISingleton
class AuthRippleController @Inject constructor(
private val sysuiContext: Context,
@@ -81,6 +83,7 @@ class AuthRippleController @Inject constructor(
private val logger: KeyguardLogger,
private val biometricUnlockController: BiometricUnlockController,
private val lightRevealScrim: LightRevealScrim,
+ private val authRippleInteractor: AuthRippleInteractor,
private val facePropertyRepository: FacePropertyRepository,
rippleView: AuthRippleView?
) :
@@ -103,6 +106,22 @@ class AuthRippleController @Inject constructor(
init()
}
+ init {
+ if (DeviceEntryUdfpsRefactor.isEnabled) {
+ rippleView?.repeatWhenAttached {
+ repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) {
+ authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource ->
+ if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) {
+ showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
+ } else {
+ showUnlockRippleInternal(BiometricSourceType.FACE)
+ }
+ }
+ }
+ }
+ }
+ }
+
@VisibleForTesting
public override fun onViewAttached() {
authController.addCallback(authControllerCallback)
@@ -114,7 +133,9 @@ class AuthRippleController @Inject constructor(
keyguardStateController.addCallback(this)
wakefulnessLifecycle.addObserver(this)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
- biometricUnlockController.addListener(biometricModeListener)
+ if (!DeviceEntryUdfpsRefactor.isEnabled) {
+ biometricUnlockController.addListener(biometricModeListener)
+ }
}
private val biometricModeListener =
@@ -122,8 +143,9 @@ class AuthRippleController @Inject constructor(
override fun onBiometricUnlockedWithKeyguardDismissal(
biometricSourceType: BiometricSourceType?
) {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode()
if (biometricSourceType != null) {
- showUnlockRipple(biometricSourceType)
+ showUnlockRippleInternal(biometricSourceType)
} else {
logger.log(TAG,
LogLevel.ERROR,
@@ -146,7 +168,13 @@ class AuthRippleController @Inject constructor(
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+ @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.")
+ fun showUnlockRipple(biometricSourceType: BiometricSourceType) {
+ DeviceEntryUdfpsRefactor.assertInLegacyMode()
+ showUnlockRippleInternal(biometricSourceType)
+ }
+
+ private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) {
val keyguardNotShowing = !keyguardStateController.isShowing
val unlockNotAllowed = !keyguardUpdateMonitor
.isUnlockingWithBiometricAllowed(biometricSourceType)
@@ -316,18 +344,6 @@ class AuthRippleController @Inject constructor(
mView.fadeDwellRipple()
}
}
-
- override fun onBiometricDetected(
- userId: Int,
- biometricSourceType: BiometricSourceType,
- isStrongBiometric: Boolean
- ) {
- // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
- if (!DeviceEntryUdfpsRefactor.isEnabled &&
- keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
- showUnlockRipple(biometricSourceType)
- }
- }
}
private val configurationChangedListener =
@@ -392,12 +408,12 @@ class AuthRippleController @Inject constructor(
}
"fingerprint" -> {
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
- showUnlockRipple(BiometricSourceType.FINGERPRINT)
+ showUnlockRippleInternal(BiometricSourceType.FINGERPRINT)
}
"face" -> {
// note: only shows when about to proceed to the home screen
pw.println("face ripple sensorLocation=$faceSensorLocation")
- showUnlockRipple(BiometricSourceType.FACE)
+ showUnlockRippleInternal(BiometricSourceType.FACE)
}
"custom" -> {
if (args.size != 3 ||
@@ -424,7 +440,7 @@ class AuthRippleController @Inject constructor(
pw.println(" custom <x-location: int> <y-location: int>")
}
- fun invalidCommand(pw: PrintWriter) {
+ private fun invalidCommand(pw: PrintWriter) {
pw.println("invalid command")
help(pw)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
index ad2136af4b86..d28dbc0ae06f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -94,6 +94,10 @@ constructor(
override fun onAuthenticationStopped() {
updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
}
+
+ override fun onAuthenticationSucceeded(requestReason: Int, userId: Int) {}
+
+ override fun onAuthenticationFailed(requestReason: Int, userId: Int) {}
}
updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index 348b54e0c7f6..c3dc2d406cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -26,20 +26,24 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.res.R
import java.util.Optional
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsSensorInteractor
@Inject
@@ -49,6 +53,7 @@ constructor(
windowManager: WindowManager,
displayStateInteractor: DisplayStateInteractor,
fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
+ biometricSettingsRepository: BiometricSettingsRepository,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val logger: SideFpsLogger,
) {
@@ -84,13 +89,24 @@ constructor(
.map { it ?: 0L }
.onEach { logger.authDurationChanged(it) }
+ private val isSettingEnabled: Flow<Boolean> =
+ biometricSettingsRepository.isFingerprintEnrolledAndEnabled
+ .flatMapLatest { enabledAndEnrolled ->
+ if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) {
+ flowOf(false)
+ } else {
+ fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+ }
+ }
+ .onEach { logger.restToUnlockSettingEnabledChanged(it) }
+
val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
- if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) {
+ if (!isProlongedTouchEnabledForDevice) {
flowOf(false)
} else {
combine(
isAvailable,
- fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
+ isSettingEnabled,
) { sfpsAvailable, isSettingEnabled ->
sfpsAvailable && isSettingEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 16e7f05fae37..96582cb56dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -114,7 +114,7 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
resources: Resources,
): Boolean {
- val passedInText: CharSequence =
+ val passedInText: String =
when (this) {
is PromptContentItemPlainText -> text
is PromptContentItemBulletedText -> text
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
index c36e0e21d021..80d37b4741e4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -121,11 +121,13 @@ constructor(
if (it.isAttachedToWindow) {
lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie?.pauseAnimation()
+ lottie?.removeAllLottieOnCompositionLoadedListener()
windowManager.get().removeView(it)
}
}
overlayView = layoutInflater.get().inflate(R.layout.sidefps_view, null, false)
+
val overlayViewModel =
SideFpsOverlayViewModel(
applicationContext,
@@ -163,8 +165,10 @@ constructor(
val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition ->
- viewModel.setLottieBounds(composition.bounds)
- overlayView.visibility = View.VISIBLE
+ if (overlayView.visibility != View.VISIBLE) {
+ viewModel.setLottieBounds(composition.bounds)
+ overlayView.visibility = View.VISIBLE
+ }
}
it.alpha = 0f
val overlayShowAnimator =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index dca0338dc8e7..0f1340a63032 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -21,6 +21,7 @@ import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.hardware.biometrics.BiometricPrompt
+import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.util.Log
import android.view.HapticFeedbackConstants
@@ -240,7 +241,7 @@ constructor(
promptSelectorInteractor.prompt
.map {
when {
- it == null -> null
+ !customBiometricPrompt() || it == null -> null
it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
else -> context.packageManager.getApplicationIcon(it.opPackageName)
@@ -258,7 +259,9 @@ constructor(
/** Custom content view for the prompt. */
val contentView: Flow<PromptContentView?> =
- promptSelectorInteractor.prompt.map { it?.contentView }.distinctUntilChanged()
+ promptSelectorInteractor.prompt
+ .map { if (customBiometricPrompt()) it?.contentView else null }
+ .distinctUntilChanged()
private val originalDescription =
promptSelectorInteractor.prompt.map { it?.description ?: "" }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 1f4be4060223..addd880f2079 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -20,13 +20,18 @@ import com.android.systemui.Flags.communalHub
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.scene.data.repository.SceneContainerRepository
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -34,16 +39,26 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
/** Encapsulates the state of communal mode. */
interface CommunalRepository {
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
+ /**
+ * A {@link StateFlow} that tracks whether communal hub is enabled (it can be disabled in
+ * settings).
+ */
+ val communalEnabledState: StateFlow<Boolean>
+
/** Whether the communal hub is showing. */
val isCommunalHubShowing: Flow<Boolean>
@@ -72,13 +87,36 @@ interface CommunalRepository {
class CommunalRepositoryImpl
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
@Background backgroundScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
private val featureFlagsClassic: FeatureFlagsClassic,
sceneContainerFlags: SceneContainerFlags,
sceneContainerRepository: SceneContainerRepository,
+ userRepository: UserRepository,
+ private val secureSettings: SecureSettings
) : CommunalRepository {
+
+ private val communalEnabledSettingState: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
+ .shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
+
+ override val communalEnabledState: StateFlow<Boolean> =
+ if (featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()) {
+ communalEnabledSettingState
+ .filterNotNull()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = true
+ )
+ } else {
+ MutableStateFlow(false)
+ }
+
override val isCommunalEnabled: Boolean
- get() = featureFlagsClassic.isEnabled(Flags.COMMUNAL_SERVICE_ENABLED) && communalHub()
+ get() = communalEnabledState.value
private val _desiredScene: MutableStateFlow<CommunalSceneKey> =
MutableStateFlow(CommunalSceneKey.DEFAULT)
@@ -115,4 +153,26 @@ constructor(
} else {
desiredScene.map { sceneKey -> sceneKey == CommunalSceneKey.Communal }
}
+
+ private fun observeSettings(userId: Int): Flow<Boolean> =
+ secureSettings
+ .observerFlow(
+ userId = userId,
+ names =
+ arrayOf(
+ GLANCEABLE_HUB_ENABLED,
+ )
+ )
+ // Force an update
+ .onStart { emit(Unit) }
+ .map { readFromSettings(userId) }
+
+ private suspend fun readFromSettings(userId: Int): Boolean =
+ withContext(backgroundDispatcher) {
+ secureSettings.getIntForUser(GLANCEABLE_HUB_ENABLED, 1, userId) == 1
+ }
+
+ companion object {
+ private const val GLANCEABLE_HUB_ENABLED = "glanceable_hub_enabled"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index 3287ed4d4991..f36547b01802 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -27,21 +27,17 @@ import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.util.kotlin.getValue
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -67,18 +63,15 @@ interface CommunalWidgetRepository {
* @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
*/
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
-
- /** Update whether the app widget host should be active. */
- fun updateAppWidgetHostActive(active: Boolean)
}
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
constructor(
- private val appWidgetManager: Optional<AppWidgetManager>,
+ appWidgetManagerOptional: Optional<AppWidgetManager>,
private val appWidgetHost: CommunalAppWidgetHost,
- @Application private val applicationScope: CoroutineScope,
+ @Background private val bgScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDao: CommunalWidgetDao,
@@ -90,41 +83,22 @@ constructor(
private val logger = Logger(logBuffer, TAG)
- override fun updateAppWidgetHostActive(active: Boolean) {
- if (active == isHostActive.value) {
- return
- }
-
- if (active) {
- appWidgetHost.startListening()
- } else {
- appWidgetHost.stopListening()
- }
- isHostActive.value = active
- }
-
- private val isHostActive = MutableStateFlow(false)
+ private val appWidgetManager by appWidgetManagerOptional
- @OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
- isHostActive.flatMapLatest { isHostActive ->
- if (!isHostActive || !appWidgetManager.isPresent) {
- return@flatMapLatest flowOf(emptyList())
- }
- communalWidgetDao
- .getWidgets()
- .map { it.map(::mapToContentModel) }
- // As this reads from a database and triggers IPCs to AppWidgetManager,
- // it should be executed in the background.
- .flowOn(bgDispatcher)
- }
+ communalWidgetDao
+ .getWidgets()
+ .map { it.mapNotNull(::mapToContentModel) }
+ // As this reads from a database and triggers IPCs to AppWidgetManager,
+ // it should be executed in the background.
+ .flowOn(bgDispatcher)
override fun addWidget(
provider: ComponentName,
priority: Int,
configurator: WidgetConfigurator?
) {
- applicationScope.launch(bgDispatcher) {
+ bgScope.launch {
val id = communalWidgetHost.allocateIdAndBindWidget(provider)
if (id == null) {
logger.e("Failed to allocate widget id to ${provider.flattenToString()}")
@@ -170,7 +144,7 @@ constructor(
}
override fun deleteWidget(widgetId: Int) {
- applicationScope.launch(bgDispatcher) {
+ bgScope.launch {
communalWidgetDao.deleteWidgetById(widgetId)
appWidgetHost.deleteAppWidgetId(widgetId)
logger.i("Deleted widget with id $widgetId.")
@@ -178,7 +152,7 @@ constructor(
}
override fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {
- applicationScope.launch(bgDispatcher) {
+ bgScope.launch {
communalWidgetDao.updateWidgetOrder(widgetIdToPriorityMap)
logger.i({ "Updated the order of widget list with ids: $str1." }) {
str1 = widgetIdToPriorityMap.toString()
@@ -189,11 +163,12 @@ constructor(
@WorkerThread
private fun mapToContentModel(
entry: Map.Entry<CommunalItemRank, CommunalWidgetItem>
- ): CommunalWidgetContentModel {
+ ): CommunalWidgetContentModel? {
val (_, widgetId) = entry.value
+ val providerInfo = appWidgetManager?.getAppWidgetInfo(widgetId) ?: return null
return CommunalWidgetContentModel(
appWidgetId = widgetId,
- providerInfo = appWidgetManager.get().getAppWidgetInfo(widgetId),
+ providerInfo = providerInfo,
priority = entry.key.rank,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 4c5871d796b1..28adb77f00e0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -37,18 +37,22 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.BooleanFlowOperators.and
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to communal mode. */
@@ -68,28 +72,34 @@ constructor(
private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
+ private val _editModeOpen = MutableStateFlow(false)
+
+ /** Whether edit mode is currently open. */
+ val editModeOpen: StateFlow<Boolean> = _editModeOpen.asStateFlow()
+
/** Whether communal features are enabled. */
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
- val isCommunalAvailable =
+ /** A {@link StateFlow} that tracks whether communal features are enabled. */
+ val communalEnabledState: StateFlow<Boolean>
+ get() = communalRepository.communalEnabledState
+
+ /** Whether communal features are enabled and available. */
+ val isCommunalAvailable: StateFlow<Boolean> =
flowOf(isCommunalEnabled)
.flatMapLatest { enabled ->
- if (enabled)
- combine(
- keyguardInteractor.isEncryptedOrLockdown,
- userRepository.selectedUserInfo,
- keyguardInteractor.isKeyguardVisible,
- keyguardInteractor.isDreaming,
- ) { isEncryptedOrLockdown, selectedUserInfo, isKeyguardVisible, isDreaming ->
- !isEncryptedOrLockdown &&
- selectedUserInfo.isMain &&
- (isKeyguardVisible || isDreaming)
- }
- else flowOf(false)
+ if (enabled) {
+ val isMainUser = userRepository.selectedUserInfo.map { it.isMain }
+ and(
+ isMainUser,
+ not(keyguardInteractor.isEncryptedOrLockdown),
+ or(keyguardInteractor.isKeyguardVisible, keyguardInteractor.isDreaming),
+ )
+ } else {
+ flowOf(false)
+ }
}
- .distinctUntilChanged()
- .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
@@ -161,6 +171,10 @@ constructor(
communalRepository.setDesiredScene(newScene)
}
+ fun setEditModeOpen(isOpen: Boolean) {
+ _editModeOpen.value = isOpen
+ }
+
/** Show the widget editor Activity. */
fun showWidgetEditor() {
editWidgetsActivityStarter.startActivity()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 237a0c005af5..4b98f1ae4fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -75,4 +75,7 @@ constructor(
_reorderingWidgets.value = false
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
+
+ /** Sets whether edit mode is currently open */
+ fun setEditModeOpen(isOpen: Boolean) = communalInteractor.setEditModeOpen(isOpen)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
new file mode 100644
index 000000000000..586df32e6561
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.kotlin.BooleanFlowOperators.or
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class CommunalAppWidgetHostStartable
+@Inject
+constructor(
+ private val appWidgetHost: CommunalAppWidgetHost,
+ private val communalInteractor: CommunalInteractor,
+ @Background private val bgScope: CoroutineScope,
+ @Main private val uiDispatcher: CoroutineDispatcher
+) : CoreStartable {
+ override fun start() {
+ or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+ // Only trigger updates on state changes, ignoring the initial false value.
+ .pairwise(false)
+ .filter { (previous, new) -> previous != new }
+ .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+ .launchIn(bgScope)
+ }
+
+ private suspend fun updateAppWidgetHostActive(active: Boolean) =
+ // Always ensure this is called on the main/ui thread.
+ withContext(uiDispatcher) {
+ if (active) {
+ appWidgetHost.startListening()
+ } else {
+ appWidgetHost.stopListening()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index c7a14f9eefe1..a2575439e4b2 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -86,6 +86,8 @@ constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ communalViewModel.setEditModeOpen(true)
+
val windowInsetsController = window.decorView.windowInsetsController
windowInsetsController?.hide(WindowInsets.Type.systemBars())
window.setDecorFitsSystemWindows(false)
@@ -138,13 +140,16 @@ constructor(
override fun onStart() {
super.onStart()
-
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_SHOWN)
}
override fun onStop() {
super.onStop()
-
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_EDIT_MODE_GONE)
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+ communalViewModel.setEditModeOpen(false)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 8d82b552fc1e..95233f701bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -25,6 +25,7 @@ import com.android.systemui.back.domain.interactor.BackActionInteractor
import com.android.systemui.biometrics.BiometricNotificationService
import com.android.systemui.clipboardoverlay.ClipboardListener
import com.android.systemui.communal.log.CommunalLoggerStartable
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
@@ -324,4 +325,11 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(CommunalLoggerStartable::class)
abstract fun bindCommunalLoggerStartable(impl: CommunalLoggerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(CommunalAppWidgetHostStartable::class)
+ abstract fun bindCommunalAppWidgetHostStartable(
+ impl: CommunalAppWidgetHostStartable
+ ): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 7a70c4ac9fab..cf7d60140aee 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -40,8 +40,7 @@ import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationSta
import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.BiometricType
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
@@ -63,10 +62,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
import com.google.errorprone.annotations.CompileTimeConstant
-import java.io.PrintWriter
-import java.util.Arrays
-import java.util.stream.Collectors
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -88,6 +83,10 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
+import java.io.PrintWriter
+import java.util.Arrays
+import java.util.stream.Collectors
+import javax.inject.Inject
/**
* API to run face authentication and detection for device entry / on keyguard (as opposed to the
@@ -165,7 +164,6 @@ constructor(
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val displayStateInteractor: DisplayStateInteractor,
- private val featureFlags: FeatureFlags,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -315,7 +313,7 @@ constructor(
// or device starts going to sleep.
merge(
powerInteractor.isAsleep,
- if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
} else {
keyguardRepository.keyguardDoneAnimationsFinished.map { true }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 08e8c2d8271f..82834387f7b8 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -8,35 +8,26 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.user.data.repository.UserRepository
-import com.android.systemui.util.kotlin.sample
import dagger.Binds
import dagger.Module
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
/** Interface for classes that can access device-entry-related application state. */
interface DeviceEntryRepository {
- /** Whether the device is immediately entering the device after a biometric unlock. */
- val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource>
-
/**
* Whether the device is unlocked.
*
@@ -85,12 +76,6 @@ constructor(
keyguardStateController: KeyguardStateController,
keyguardRepository: KeyguardRepository,
) : DeviceEntryRepository {
- override val enteringDeviceFromBiometricUnlock =
- keyguardRepository.biometricUnlockState
- .filter { BiometricUnlockModel.dismissesKeyguard(it) }
- .sample(
- keyguardRepository.biometricUnlockSource.filterNotNull(),
- )
private val _isUnlocked = MutableStateFlow(false)
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
new file mode 100644
index 000000000000..337fe1ea7d98
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Business logic for device entry auth ripple interactions. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class AuthRippleInteractor
+@Inject
+constructor(
+ deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
+ deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+) {
+ private val showUnlockRippleFromDeviceEntryIcon: Flow<BiometricUnlockSource> =
+ deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsSupported ->
+ if (isUdfpsSupported) {
+ deviceEntrySourceInteractor.deviceEntryFromDeviceEntryIcon.map {
+ BiometricUnlockSource.FINGERPRINT_SENSOR
+ }
+ } else {
+ emptyFlow()
+ }
+ }
+
+ private val showUnlockRippleFromBiometricUnlock: Flow<BiometricUnlockSource> =
+ deviceEntrySourceInteractor.deviceEntryFromBiometricSource
+ val showUnlockRipple: Flow<BiometricUnlockSource> =
+ merge(
+ showUnlockRippleFromDeviceEntryIcon,
+ showUnlockRippleFromBiometricUnlock,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 649a9715ffea..782bce494d11 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -46,7 +46,7 @@ import kotlinx.coroutines.flow.onStart
class DeviceEntryHapticsInteractor
@Inject
constructor(
- deviceEntryInteractor: DeviceEntryInteractor,
+ deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor,
fingerprintPropertyRepository: FingerprintPropertyRepository,
@@ -80,7 +80,7 @@ constructor(
}
val playSuccessHaptic: Flow<Unit> =
- deviceEntryInteractor.enteringDeviceFromBiometricUnlock
+ deviceEntrySourceInteractor.deviceEntryFromBiometricSource
.sample(
combine(
powerButtonSideFpsEnrolled,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 09853578d3f1..73389cb1bdce 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -23,7 +23,6 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
@@ -31,7 +30,6 @@ import com.android.systemui.scene.shared.model.SceneModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
@@ -55,7 +53,7 @@ class DeviceEntryInteractor
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- repository: DeviceEntryRepository,
+ private val repository: DeviceEntryRepository,
private val authenticationInteractor: AuthenticationInteractor,
private val sceneInteractor: SceneInteractor,
deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
@@ -63,9 +61,6 @@ constructor(
flags: SceneContainerFlags,
deviceUnlockedInteractor: DeviceUnlockedInteractor,
) {
- val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
- repository.enteringDeviceFromBiometricUnlock
-
/**
* Whether the device is unlocked.
*
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
new file mode 100644
index 000000000000..d4f76a84c016
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+
+/**
+ * Hosts application business logic related to the source of the user entering the device. Note: The
+ * source of the user entering the device isn't equivalent to the reason the device is unlocked.
+ *
+ * For example, the user successfully enters the device when they dismiss the lockscreen via a
+ * bypass biometric or, if the device is already unlocked, by triggering an affordance that
+ * dismisses the lockscreen.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntrySourceInteractor
+@Inject
+constructor(
+ keyguardInteractor: KeyguardInteractor,
+) {
+ val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> =
+ keyguardInteractor.biometricUnlockState
+ .filter { BiometricUnlockModel.dismissesKeyguard(it) }
+ .sample(
+ keyguardInteractor.biometricUnlockSource.filterNotNull(),
+ )
+
+ private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
+ val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
+ attemptEnterDeviceFromDeviceEntryIcon
+ .sample(keyguardInteractor.isKeyguardDismissible)
+ .filter { it } // only send events if the keyguard is dismissible
+ .map {} // map to Unit
+
+ suspend fun attemptEnterDeviceFromDeviceEntryIcon() {
+ attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 3f7c152f47d8..c69c9ef93761 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -44,11 +44,11 @@ object Flags {
// 100 - notification
// TODO(b/297792660): Tracking Bug
@JvmField val UNCLEARED_TRANSIENT_HUN_FIX =
- unreleasedFlag("uncleared_transient_hun_fix", teamfood = true)
+ releasedFlag("uncleared_transient_hun_fix")
// TODO(b/298308067): Tracking Bug
@JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX =
- unreleasedFlag("swipe_uncleared_transient_view_fix", teamfood = true)
+ releasedFlag("swipe_uncleared_transient_view_fix")
// TODO(b/254512751): Tracking Bug
val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
@@ -102,12 +102,6 @@ object Flags {
default = true
)
- /** Only notify group expansion listeners when a change happens. */
- // TODO(b/292213543): Tracking Bug
- @JvmField
- val NOTIFICATION_GROUP_EXPANSION_CHANGE =
- releasedFlag("notification_group_expansion_change")
-
// TODO(b/301955929)
@JvmField
val NOTIF_LS_BACKGROUND_THREAD =
@@ -226,19 +220,6 @@ object Flags {
@JvmField
val WALLPAPER_PICKER_PREVIEW_ANIMATION = releasedFlag("wallpaper_picker_preview_animation")
- /**
- * TODO(b/278086361): Tracking bug
- * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
- * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
- * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
- *
- * This flag is under development; some types of unlock may not animate properly if you enable
- * it.
- */
- @JvmField
- val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
- unreleasedFlag("keyguard_wm_state_refactor")
-
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 3de9e68909cf..a95ddb5a0201 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2437,6 +2437,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
return true;
}
});
+ mGlobalActionsLayout.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
mGlobalActionsLayout.setRotationListener(this::onRotate);
mGlobalActionsLayout.setAdapter(mAdapter);
mContainer = findViewById(com.android.systemui.res.R.id.global_actions_container);
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java
index 5deea9bc737a..98642d75f907 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialogDelegate.java
@@ -16,17 +16,25 @@
package com.android.systemui.keyboard;
-import android.content.Context;
import android.view.WindowManager;
import com.android.systemui.statusbar.phone.SystemUIDialog;
-public class BluetoothDialog extends SystemUIDialog {
+import javax.inject.Inject;
- public BluetoothDialog(Context context) {
- super(context);
+public class BluetoothDialogDelegate implements SystemUIDialog.Delegate{
- getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
- setShowForAllUsers(true);
+ private final SystemUIDialog.Factory mSystemUIDialogFactory;
+ @Inject
+ public BluetoothDialogDelegate(SystemUIDialog.Factory systemUIDialogFactory) {
+ mSystemUIDialogFactory = systemUIDialogFactory;
+ }
+
+ @Override
+ public SystemUIDialog createDialog() {
+ SystemUIDialog dialog = mSystemUIDialogFactory.create(this);
+ dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ dialog.setShowForAllUsers(true);
+ return dialog;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 1cdbe6fed58a..17e3ca6f1a8c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -52,6 +52,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.systemui.CoreStartable;
import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
@@ -109,6 +110,7 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang
private final Provider<LocalBluetoothManager> mBluetoothManagerProvider;
private final SecureSettings mSecureSettings;
+ private final BluetoothDialogDelegate mBluetoothDialogDelegate;
private boolean mEnabled;
private String mKeyboardName;
@@ -121,16 +123,20 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang
private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN;
private int mScanAttempt = 0;
private ScanCallback mScanCallback;
- private BluetoothDialog mDialog;
+ private SystemUIDialog mDialog;
private int mState;
@Inject
- public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider,
- SecureSettings secureSettings) {
+ public KeyboardUI(
+ Context context,
+ Provider<LocalBluetoothManager> bluetoothManagerProvider,
+ SecureSettings secureSettings,
+ BluetoothDialogDelegate bluetoothDialogDelegate) {
mContext = context;
this.mBluetoothManagerProvider = bluetoothManagerProvider;
mSecureSettings = secureSettings;
+ mBluetoothDialogDelegate = bluetoothDialogDelegate;
}
@Override
@@ -437,7 +443,7 @@ public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChang
new BluetoothDialogClickListener();
DialogInterface.OnDismissListener dismissListener =
new BluetoothDialogDismissListener();
- mDialog = new BluetoothDialog(mContext);
+ mDialog = mBluetoothDialogDelegate.createDialog();
mDialog.setTitle(R.string.enable_bluetooth_title);
mDialog.setMessage(R.string.enable_bluetooth_message);
mDialog.setPositiveButton(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
index 4ed812010100..5ef5ef19c809 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
@@ -43,4 +43,13 @@ class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogB
{ "new sticky keys state received: $str1" }
)
}
-} \ No newline at end of file
+
+ fun logNewSettingValue(enabled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.INFO,
+ { bool1 = enabled },
+ { "sticky key setting changed, new state: ${if (bool1) "enabled" else "disabled"}" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 34d288815570..ec29bd6014ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -19,8 +19,10 @@ package com.android.systemui.keyboard.stickykeys.data.repository
import android.hardware.input.InputManager
import android.hardware.input.InputManager.StickyModifierStateListener
import android.hardware.input.StickyModifierState
+import android.provider.Settings
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
import com.android.systemui.keyboard.stickykeys.shared.model.Locked
@@ -30,14 +32,19 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
interface StickyKeysRepository {
@@ -45,11 +52,15 @@ interface StickyKeysRepository {
val settingEnabled: Flow<Boolean>
}
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
class StickyKeysRepositoryImpl
@Inject
constructor(
private val inputManager: InputManager,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val secureSettings: SecureSettings,
+ userRepository: UserRepository,
private val stickyKeysLogger: StickyKeysLogger,
) : StickyKeysRepository {
@@ -66,8 +77,26 @@ constructor(
.onEach { stickyKeysLogger.logNewStickyKeysReceived(it) }
.flowOn(backgroundDispatcher)
- // TODO(b/319837892): Implement reading actual setting
- override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
+ override val settingEnabled: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { stickyKeySettingObserver(it.id) }
+ .flowOn(backgroundDispatcher)
+
+ private fun stickyKeySettingObserver(userId: Int): Flow<Boolean> {
+ return secureSettings
+ .observerFlow(userId, SETTING_KEY)
+ .onStart { emit(Unit) }
+ .map { isSettingEnabledForCurrentUser(userId) }
+ .distinctUntilChanged()
+ .onEach { stickyKeysLogger.logNewSettingValue(it) }
+ }
+
+ private fun isSettingEnabledForCurrentUser(userId: Int) =
+ secureSettings.getIntForUser(
+ /* name= */ SETTING_KEY,
+ /* default= */ 0,
+ /* userHandle= */ userId
+ ) != 0
private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
val keys = linkedMapOf<ModifierKey, Locked>()
@@ -88,5 +117,6 @@ constructor(
companion object {
const val TAG = "StickyKeysRepositoryImpl"
+ const val SETTING_KEY = Settings.Secure.ACCESSIBILITY_STICKY_KEYS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e2ab20e29e2d..f10b87ee1cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -77,7 +77,6 @@ import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
@@ -329,7 +328,7 @@ public class KeyguardService extends Service {
mFlags = featureFlags;
mPowerInteractor = powerInteractor;
- if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
WindowManagerLockscreenVisibilityViewBinder.bind(
wmLockscreenVisibilityViewModel,
wmLockscreenVisibilityManager,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 01ba0d214a97..53c81e537708 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -419,7 +419,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
// TODO(b/278086361): Refactor in-window animations.
- return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+ return !KeyguardWmStateRefactor.isEnabled &&
isSupportedLauncherUnderneath() &&
// If the launcher is underneath, but we're about to launch an activity, don't do
// the animations since they won't be visible.
@@ -866,7 +866,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
}
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
- if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled) {
val surfaceHeight: Int =
surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
@@ -1005,7 +1005,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
- if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled) {
keyguardViewController.hide(
surfaceBehindRemoteAnimationStartTime,
0 /* fadeOutDuration */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 50caf17f71dd..8e3b19609142 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -139,7 +139,6 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
@@ -175,8 +174,6 @@ import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -186,6 +183,7 @@ import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1051,7 +1049,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
transit, apps, wallpapers, nonApps, finishedCallback);
}
@@ -1061,7 +1059,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override // Binder interface
public void onAnimationCancelled() {
cancelKeyguardExitAnimation();
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
}
}
@@ -2757,7 +2755,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(() -> {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
- if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager if flag is enabled.
return;
}
@@ -2811,7 +2809,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
setShowingLocked(true, hidingOrGoingAway /* force */);
mHiding = false;
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled directly in StatusBarKeyguardViewManager if enabled.
mKeyguardViewControllerLazy.get().show(options);
}
@@ -2888,7 +2886,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
// Handled in WmLockscreenVisibilityManager if flag is enabled.
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Don't actually hide the Keyguard at the moment, wait for window manager
// until it tells us it's safe to do so with startKeyguardExitAnimation.
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager
@@ -2994,7 +2992,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
} else {
Log.d(TAG, "Hiding keyguard while occluded. Just hide the keyguard view and exit.");
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
mKeyguardViewControllerLazy.get().hide(
mSystemClock.uptimeMillis() + mHideAnimation.getStartOffset(),
mHideAnimation.getDuration());
@@ -3030,7 +3028,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// If the flag is enabled, remote animation state is handled in
// WmLockscreenVisibilityManager.
if (finishedCallback != null
- && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ && !KeyguardWmStateRefactor.isEnabled()) {
// There will not execute animation, send a finish callback to ensure the remote
// animation won't hang there.
try {
@@ -3056,7 +3054,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
new IRemoteAnimationFinishedCallback() {
@Override
public void onAnimationFinished() throws RemoteException {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
@@ -3088,7 +3086,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// it will dismiss the panel in that case.
} else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager. Other logic in this class will
// short circuit when this is null.
mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
@@ -3112,7 +3110,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
createInteractionJankMonitorConf(
CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled directly in StatusBarKeyguardViewManager if enabled.
mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
}
@@ -3131,7 +3129,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
try {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
@@ -3163,7 +3161,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onAnimationEnd(Animator animation) {
try {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
@@ -3176,7 +3174,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void onAnimationCancel(Animator animation) {
try {
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
finishedCallback.onAnimationFinished();
}
} catch (RemoteException e) {
@@ -3341,7 +3339,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
- if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager.
mActivityTaskManagerService.keyguardGoingAway(flags);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
new file mode 100644
index 000000000000..ddccc5d9e96d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard wm state refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardWmStateRefactor {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.keyguardWmStateRefactor()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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 14371949c9c8..1c6056c3b9d0 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
@@ -101,7 +101,7 @@ interface KeyguardRepository {
* Whether the device is locked or unlocked right now. This is true when keyguard has been
* dismissed or can be dismissed by a swipe
*/
- val isKeyguardUnlocked: StateFlow<Boolean>
+ val isKeyguardDismissible: StateFlow<Boolean>
/**
* Observable for the signal that keyguard is about to go away.
@@ -388,7 +388,7 @@ constructor(
}
.distinctUntilChanged()
- override val isKeyguardUnlocked: StateFlow<Boolean> =
+ override val isKeyguardDismissible: StateFlow<Boolean> =
conflatedCallbackFlow {
val callback =
object : KeyguardStateController.Callback {
@@ -396,7 +396,7 @@ constructor(
trySendWithFailureLogging(
keyguardStateController.isUnlocked,
TAG,
- "updated isKeyguardUnlocked due to onUnlockedChanged"
+ "updated isKeyguardDismissible due to onUnlockedChanged"
)
}
@@ -404,7 +404,7 @@ constructor(
trySendWithFailureLogging(
keyguardStateController.isUnlocked,
TAG,
- "updated isKeyguardUnlocked due to onKeyguardShowingChanged"
+ "updated isKeyguardDismissible due to onKeyguardShowingChanged"
)
}
}
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 8b2b45f1bf57..3965648bd224 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
@@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
@@ -230,7 +230,7 @@ constructor(
combine(
startedKeyguardTransitionStep,
keyguardInteractor.statusBarState,
- keyguardInteractor.isKeyguardUnlocked,
+ keyguardInteractor.isKeyguardDismissible,
::Triple
),
::toQuad
@@ -307,7 +307,7 @@ constructor(
}
private fun listenForLockscreenToGone() {
- if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
return
}
@@ -324,7 +324,7 @@ constructor(
}
private fun listenForLockscreenToGoneDragging() {
- if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
return
}
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 33b6373d5876..acbd9fb4c407 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
@@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
@@ -217,7 +217,7 @@ constructor(
}
private fun listenForPrimaryBouncerToGone() {
- if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled) {
// This is handled in KeyguardSecurityContainerController and
// StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
// transition vs. listening to legacy state flags.
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 91747e0f69a0..22d11d08054e 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
@@ -32,6 +32,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
@@ -98,7 +99,7 @@ constructor(
val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */
- val isDozing: Flow<Boolean> = repository.isDozing
+ val isDozing: StateFlow<Boolean> = repository.isDozing
/** Receive an event for doze time tick */
val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
@@ -162,8 +163,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 dismissible or not. */
+ val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
/** Whether the keyguard is occluded (covered by an activity). */
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
@@ -194,6 +195,9 @@ constructor(
/** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState
+ /** Source of the most recent biometric unlock, such as fingerprint or face. */
+ val biometricUnlockSource: Flow<BiometricUnlockSource?> = repository.biometricUnlockSource
+
/**
* Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
* side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index a02e8ac84a75..703bb879533c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -32,6 +32,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
@@ -48,6 +49,7 @@ object DeviceEntryIconViewBinder {
@SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
+ applicationScope: CoroutineScope,
view: DeviceEntryIconView,
viewModel: DeviceEntryIconViewModel,
fgViewModel: DeviceEntryForegroundViewModel,
@@ -69,7 +71,7 @@ object DeviceEntryIconViewBinder {
view,
HapticFeedbackConstants.CONFIRM,
)
- viewModel.onLongPress()
+ applicationScope.launch { viewModel.onLongPress() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index bc6c7cbf35fb..ad589dfcff9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -120,8 +120,20 @@ constructor(
} else {
connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
}
- connect(nicId, START, PARENT_ID, START)
- connect(nicId, END, PARENT_ID, END)
+ connect(
+ nicId,
+ START,
+ PARENT_ID,
+ START,
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ )
+ connect(
+ nicId,
+ END,
+ PARENT_ID,
+ END,
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ )
constrainHeight(
nicId,
context.resources.getDimensionPixelSize(R.dimen.notification_shelf_height)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index a1b3f270f642..fe4f07d022dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -31,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.Flags
+import com.android.systemui.customization.R as customizationR
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -160,16 +161,14 @@ constructor(
var largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_padding_top
+ customizationR.dimen.small_clock_padding_top
) +
context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
largeClockTopMargin += getDimen(DATE_WEATHER_VIEW_HEIGHT)
largeClockTopMargin += getDimen(ENHANCED_SMARTSPACE_HEIGHT)
if (!keyguardClockViewModel.useLargeClock) {
largeClockTopMargin -=
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
}
connect(R.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin)
constrainHeight(R.id.lockscreen_clock_view_large, WRAP_CONTENT)
@@ -177,18 +176,15 @@ constructor(
constrainWidth(R.id.lockscreen_clock_view, WRAP_CONTENT)
constrainHeight(
R.id.lockscreen_clock_view,
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
)
connect(
R.id.lockscreen_clock_view,
START,
PARENT_ID,
START,
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.clock_padding_start
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.clock_padding_start) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
)
var smallClockTopMargin =
if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
@@ -199,9 +195,7 @@ constructor(
}
if (keyguardClockViewModel.useLargeClock) {
smallClockTopMargin -=
- context.resources.getDimensionPixelSize(
- com.android.systemui.customization.R.dimen.small_clock_height
- )
+ context.resources.getDimensionPixelSize(customizationR.dimen.small_clock_height)
}
connect(R.id.lockscreen_clock_view, TOP, PARENT_ID, TOP, smallClockTopMargin)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 0bf9ad02eb58..3fc9b4200f35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -31,6 +31,7 @@ import com.android.keyguard.LockIconView
import com.android.keyguard.LockIconViewController
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.biometrics.AuthController
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
@@ -46,6 +47,7 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
/** Includes the device entry icon. */
@@ -53,6 +55,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
class DefaultDeviceEntrySection
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
private val windowManager: WindowManager,
@@ -91,6 +94,7 @@ constructor(
if (DeviceEntryUdfpsRefactor.isEnabled) {
constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let {
DeviceEntryIconViewBinder.bind(
+ applicationScope,
it,
deviceEntryIconViewModel.get(),
deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 8c5e9b4c6817..d75a72f91061 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -18,7 +18,6 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
-import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
@@ -67,7 +66,6 @@ constructor(
notificationStackSizeCalculator,
mainDispatcher,
) {
- private val smartSpaceBarrier = View.generateViewId()
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 37842a84c3d3..2f99719df36c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -31,7 +31,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.shared.R
+import com.android.systemui.res.R as R
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import dagger.Lazy
import javax.inject.Inject
@@ -100,94 +101,94 @@ constructor(
if (!migrateClocksToBlueprint()) {
return
}
+ val horizontalPaddingStart =
+ context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
+ val horizontalPaddingEnd =
+ context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end) +
+ context.resources.getDimensionPixelSize(R.dimen.status_view_margin_horizontal)
constraintSet.apply {
// migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
- constrainHeight(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
- constrainWidth(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
connect(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
- context.resources.getDimensionPixelSize(
- com.android.systemui.res.R.dimen.below_clock_padding_start
- )
+ horizontalPaddingStart
)
- constrainWidth(R.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
connect(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
ConstraintSet.TOP,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.TOP
)
connect(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
ConstraintSet.BOTTOM,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.BOTTOM
)
connect(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
ConstraintSet.START,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.END,
4
)
// migrate addSmartspaceView from KeyguardClockSwitchController
- constrainHeight(R.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainHeight(sharedR.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
connect(
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
- context.resources.getDimensionPixelSize(
- com.android.systemui.res.R.dimen.below_clock_padding_start
- )
+ horizontalPaddingStart
)
connect(
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.END,
if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
- else com.android.systemui.res.R.id.split_shade_guideline,
+ else R.id.split_shade_guideline,
ConstraintSet.END,
- context.resources.getDimensionPixelSize(
- com.android.systemui.res.R.dimen.below_clock_padding_end
- )
+ horizontalPaddingEnd
)
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- clear(R.id.date_smartspace_view, ConstraintSet.TOP)
+ clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP)
connect(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.BOTTOM,
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.TOP
)
} else {
- clear(R.id.date_smartspace_view, ConstraintSet.BOTTOM)
+ clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM)
connect(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.TOP,
- com.android.systemui.res.R.id.lockscreen_clock_view,
+ R.id.lockscreen_clock_view,
ConstraintSet.BOTTOM
)
connect(
- R.id.bc_smartspace_view,
+ sharedR.id.bc_smartspace_view,
ConstraintSet.TOP,
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
ConstraintSet.BOTTOM
)
}
createBarrier(
- com.android.systemui.res.R.id.smart_space_barrier_bottom,
+ R.id.smart_space_barrier_bottom,
Barrier.BOTTOM,
0,
*intArrayOf(
- R.id.bc_smartspace_view,
- R.id.date_smartspace_view,
- R.id.weather_smartspace_view,
+ sharedR.id.bc_smartspace_view,
+ sharedR.id.date_smartspace_view,
+ sharedR.id.weather_smartspace_view,
)
)
}
@@ -212,7 +213,7 @@ constructor(
private fun updateVisibility(constraintSet: ConstraintSet) {
constraintSet.apply {
setVisibility(
- R.id.weather_smartspace_view,
+ sharedR.id.weather_smartspace_view,
when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
true -> ConstraintSet.GONE
false ->
@@ -223,7 +224,7 @@ constructor(
}
)
setVisibility(
- R.id.date_smartspace_view,
+ sharedR.id.date_smartspace_view,
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
else ConstraintSet.VISIBLE
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index eacaa40de821..a3d54532411c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -20,6 +20,7 @@ import android.animation.FloatEvaluator
import android.animation.IntEvaluator
import com.android.keyguard.KeyguardViewController
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntrySourceInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -56,6 +57,7 @@ constructor(
private val sceneContainerFlags: SceneContainerFlags,
private val keyguardViewController: Lazy<KeyguardViewController>,
private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
) {
private val intEvaluator = IntEvaluator()
private val floatEvaluator = FloatEvaluator()
@@ -208,14 +210,13 @@ constructor(
}
}
- fun onLongPress() {
- // TODO (b/309804148): play auth ripple via an interactor
-
+ suspend fun onLongPress() {
if (sceneContainerFlags.isEnabled()) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
}
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
}
private fun DeviceEntryIconView.IconType.toAccessibilityHintType():
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 693e3b7506fc..ca9c8571f6b9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -20,7 +20,7 @@ import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Point
import androidx.annotation.VisibleForTesting
-import androidx.core.animation.doOnEnd
+import androidx.core.animation.addListener
import com.android.systemui.Flags
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
@@ -30,15 +30,18 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.DozeServiceHost
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,13 +49,14 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsProgressBarViewModel
@Inject
@@ -63,9 +67,11 @@ constructor(
// todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
// DozeInteractor as DozeServiceHost already depends on DozeInteractor.
private val dozeServiceHost: DozeServiceHost,
+ private val keyguardInteractor: KeyguardInteractor,
displayStateInteractor: DisplayStateInteractor,
@Main private val mainDispatcher: CoroutineDispatcher,
@Application private val applicationScope: CoroutineScope,
+ private val powerInteractor: PowerInteractor,
) {
private val _progress = MutableStateFlow(0.0f)
private val _visible = MutableStateFlow(false)
@@ -176,48 +182,54 @@ constructor(
return@collectLatest
}
animatorJob =
- combine(
- sfpsSensorInteractor.authenticationDuration,
- fpAuthRepository.authenticationStatus,
- ::Pair
- )
- .onEach { (authDuration, authStatus) ->
- when (authStatus) {
- is AcquiredFingerprintAuthenticationStatus -> {
- if (authStatus.fingerprintCaptureStarted) {
- _visible.value = true
- dozeServiceHost.fireSideFpsAcquisitionStarted()
- _animator?.cancel()
- _animator =
- ValueAnimator.ofFloat(0.0f, 1.0f)
- .setDuration(authDuration)
- .apply {
- addUpdateListener {
- _progress.value = it.animatedValue as Float
- }
- addListener(
- doOnEnd {
- if (_progress.value == 0.0f) {
- _visible.value = false
- }
+ sfpsSensorInteractor.authenticationDuration
+ .flatMapLatest { authDuration ->
+ _animator?.cancel()
+ fpAuthRepository.authenticationStatus.map { authStatus ->
+ when (authStatus) {
+ is AcquiredFingerprintAuthenticationStatus -> {
+ if (authStatus.fingerprintCaptureStarted) {
+ if (keyguardInteractor.isDozing.value) {
+ dozeServiceHost.fireSideFpsAcquisitionStarted()
+ } else {
+ powerInteractor
+ .wakeUpForSideFingerprintAcquisition()
+ }
+ _animator?.cancel()
+ _animator =
+ ValueAnimator.ofFloat(0.0f, 1.0f)
+ .setDuration(authDuration)
+ .apply {
+ addUpdateListener {
+ _progress.value =
+ it.animatedValue as Float
}
- )
- }
- _animator?.start()
- } else if (authStatus.fingerprintCaptureCompleted) {
- onFingerprintCaptureCompleted()
- } else {
- // Abandoned FP Auth attempt
- _animator?.reverse()
+ addListener(
+ onEnd = {
+ if (_progress.value == 0.0f) {
+ _visible.value = false
+ }
+ },
+ onStart = { _visible.value = true },
+ onCancel = { _visible.value = false }
+ )
+ }
+ _animator?.start()
+ } else if (authStatus.fingerprintCaptureCompleted) {
+ onFingerprintCaptureCompleted()
+ } else {
+ // Abandoned FP Auth attempt
+ _animator?.reverse()
+ }
}
+ is ErrorFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is FailFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ is SuccessFingerprintAuthenticationStatus ->
+ onFingerprintCaptureCompleted()
+ else -> Unit
}
- is ErrorFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- is FailFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- is SuccessFingerprintAuthenticationStatus ->
- onFingerprintCaptureCompleted()
- else -> Unit
}
}
.flowOn(mainDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
index 171656a48e58..ce64ac1cbcd6 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
@@ -117,4 +117,13 @@ class SideFpsLogger @Inject constructor(@BouncerLog private val buffer: LogBuffe
{ "SideFpsSensor auth duration changed: $long1" }
)
}
+
+ fun restToUnlockSettingEnabledChanged(enabled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { bool1 = enabled },
+ { "restToUnlockSettingEnabled: $bool1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 23ee00d88fdc..a3029b284934 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -146,7 +146,7 @@ constructor(
null,
UserHandle.ALL
)
- userTracker.addCallback(userTrackerCallback, mainExecutor)
+ userTracker.addCallback(userTrackerCallback, backgroundExecutor)
loadSavedComponents()
}
}
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 fa03dc245745..b3d848c2d23b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -34,6 +34,8 @@ import androidx.annotation.VisibleForTesting
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
+import com.android.internal.jank.Cuj.CUJ_BACK_PANEL_ARROW
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -86,6 +88,7 @@ internal constructor(
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
private val latencyTracker: LatencyTracker,
+ private val interactionJankMonitor: InteractionJankMonitor,
) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
/**
@@ -103,6 +106,7 @@ internal constructor(
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
private val latencyTracker: LatencyTracker,
+ private val interactionJankMonitor: InteractionJankMonitor,
) {
/** Construct a [BackPanelController]. */
fun create(context: Context): BackPanelController {
@@ -115,6 +119,7 @@ internal constructor(
vibratorHelper,
configurationController,
latencyTracker,
+ interactionJankMonitor
)
backPanelController.init()
return backPanelController
@@ -183,7 +188,7 @@ internal constructor(
/* Arrow is animating in */
ENTRY,
- /* could be entry, neutral, or stretched, releasing will commit back */
+ /* releasing will commit back */
ACTIVE,
/* releasing will cancel back */
@@ -813,7 +818,7 @@ internal constructor(
scale =
when (currentState) {
GestureState.ACTIVE,
- GestureState.FLUNG, -> params.activeIndicator.scale
+ GestureState.FLUNG -> params.activeIndicator.scale
GestureState.COMMITTED -> params.committedIndicator.scale
else -> params.preThresholdIndicator.scale
},
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 58e042868607..91c86dff34ea 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -84,6 +84,7 @@ import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -261,7 +262,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private boolean mIsTrackpadThreeFingerSwipe;
private boolean mIsButtonForcedVisible;
- private InputMonitor mInputMonitor;
+ private InputMonitorCompat mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
private NavigationEdgeBackPlugin mEdgeBackPlugin;
@@ -665,10 +666,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
// Register input event receiver
- mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput(
- "edge-swipe", mDisplayId);
- mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
- mInputMonitor.getInputChannel(), Looper.getMainLooper(),
+ mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
+ mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 958ace358816..21de185ee838 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -26,6 +26,8 @@ import android.content.res.Configuration;
import android.database.ContentObserver;
import android.os.BatteryManager;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
@@ -95,6 +97,7 @@ public class PowerUI implements
private Future mLastShowWarningTask;
private boolean mEnableSkinTemperatureWarning;
private boolean mEnableUsbTemperatureAlarm;
+ private final HandlerThread mHandlerThread;
private int mLowBatteryAlertCloseLevel;
private final int[] mLowBatteryReminderLevels = new int[2];
@@ -167,6 +170,8 @@ public class PowerUI implements
mPowerManager = powerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
mUserTracker = userTracker;
+ mHandlerThread = new HandlerThread("PowerUI");
+ mHandlerThread.start();
}
public void start() {
@@ -185,7 +190,8 @@ public class PowerUI implements
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
// Check to see if we need to let the user know that the phone previously shut down due
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index d9e3e55c1ad3..3f8834af3ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -119,6 +119,11 @@ constructor(
}
}
+ /** Wakes up the device for the Side FPS acquisition event. */
+ fun wakeUpForSideFingerprintAcquisition() {
+ repository.wakeUp("SFPS_FP_ACQUISITION_STARTED", PowerManager.WAKE_REASON_BIOMETRIC)
+ }
+
/**
* Called from [KeyguardService] to inform us that the device has started waking up. This is the
* canonical source of wakefulness information for System UI. This method should not be called
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 8e1b00d825aa..7a4be3fdc93b 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -23,11 +23,11 @@ import android.view.Gravity.END
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import com.android.settingslib.Utils
import com.android.systemui.res.R
-import com.android.systemui.animation.view.LaunchableFrameLayout
import com.android.systemui.statusbar.events.BackgroundAnimatableView
class OngoingPrivacyChip @JvmOverloads constructor(
@@ -35,7 +35,7 @@ class OngoingPrivacyChip @JvmOverloads constructor(
attrs: AttributeSet? = null,
defStyleAttrs: Int = 0,
defStyleRes: Int = 0
-) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
+) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
private var configuration: Configuration
private var iconMargin = 0
@@ -43,6 +43,8 @@ class OngoingPrivacyChip @JvmOverloads constructor(
private var iconColor = 0
private val iconsContainer: LinearLayout
+ val launchableContentView
+ get() = iconsContainer
var privacyList = emptyList<PrivacyItem>()
set(value) {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index 76ef8a2b813c..f121630d180e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -26,9 +26,9 @@ import android.content.pm.PackageManager
import android.os.UserHandle
import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
-import android.view.View
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
+import androidx.core.view.isVisible
import com.android.internal.logging.UiEventLogger
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.appops.AppOpsController
@@ -214,7 +214,7 @@ class PrivacyDialogControllerV2(
* @param context A context to use to create the dialog.
* @see filterAndSelect
*/
- fun showDialog(context: Context, view: View? = null) {
+ fun showDialog(context: Context, privacyChip: OngoingPrivacyChip? = null) {
dismissDialog()
backgroundExecutor.execute {
val usage = permGroupUsage()
@@ -277,8 +277,8 @@ class PrivacyDialogControllerV2(
)
d.setShowForAllUsers(true)
d.addOnDismissListener(onDialogDismissed)
- if (view != null) {
- val controller = getPrivacyDialogController(view)
+ if (privacyChip != null) {
+ val controller = getPrivacyDialogController(privacyChip)
if (controller == null) {
d.show()
} else {
@@ -296,10 +296,13 @@ class PrivacyDialogControllerV2(
}
}
- private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? {
- val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null
+ private fun getPrivacyDialogController(
+ source: OngoingPrivacyChip
+ ): DialogLaunchAnimator.Controller? {
+ val delegate =
+ DialogLaunchAnimator.Controller.fromView(source.launchableContentView) ?: return null
return object : DialogLaunchAnimator.Controller by delegate {
- override fun shouldAnimateExit() = false
+ override fun shouldAnimateExit() = source.isVisible
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
index adea26e5aa26..e1ec338cec6f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -18,6 +18,8 @@ package com.android.systemui.qs.pipeline.dagger
import android.content.res.Resources
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddableList
import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
@@ -51,6 +53,16 @@ interface BaseAutoAddableModule {
)
.toSet()
}
+
+ @Provides
+ @ElementsIntoSet
+ fun providesA11yShortcutAutoAddable(
+ a11yShortcutAutoAddableFactory: A11yShortcutAutoAddable.Factory
+ ): Set<AutoAddable> {
+ return A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(
+ a11yShortcutAutoAddableFactory
+ )
+ }
}
@Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
index bcd09bd877fd..dc39c97bc9ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt
@@ -25,6 +25,7 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.ResolveInfoFlags
import android.os.UserHandle
import android.service.quicksettings.TileService
+import androidx.annotation.GuardedBy
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.shared.model.PackageChangeModel
import com.android.systemui.dagger.SysUISingleton
@@ -32,12 +33,13 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.kotlin.isComponentActuallyEnabled
import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
interface InstalledTilesComponentRepository {
@@ -49,33 +51,39 @@ class InstalledTilesComponentRepositoryImpl
@Inject
constructor(
@Application private val applicationContext: Context,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val backgroundScope: CoroutineScope,
private val packageChangeRepository: PackageChangeRepository
) : InstalledTilesComponentRepository {
- override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> {
- /*
- * In order to query [PackageManager] for different users, this implementation will call
- * [Context.createContextAsUser] and retrieve the [PackageManager] from that context.
- */
- val packageManager =
- if (applicationContext.userId == userId) {
- applicationContext.packageManager
- } else {
- applicationContext
- .createContextAsUser(
- UserHandle.of(userId),
- /* flags */ 0,
- )
- .packageManager
+ @GuardedBy("userMap") private val userMap = mutableMapOf<Int, Flow<Set<ComponentName>>>()
+
+ override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> =
+ synchronized(userMap) {
+ userMap.getOrPut(userId) {
+ /*
+ * In order to query [PackageManager] for different users, this implementation will
+ * call [Context.createContextAsUser] and retrieve the [PackageManager] from that
+ * context.
+ */
+ val packageManager =
+ if (applicationContext.userId == userId) {
+ applicationContext.packageManager
+ } else {
+ applicationContext
+ .createContextAsUser(
+ UserHandle.of(userId),
+ /* flags */ 0,
+ )
+ .packageManager
+ }
+ packageChangeRepository
+ .packageChanged(UserHandle.of(userId))
+ .onStart { emit(PackageChangeModel.Empty) }
+ .map { reloadComponents(userId, packageManager) }
+ .distinctUntilChanged()
+ .shareIn(backgroundScope, SharingStarted.WhileSubscribed(), replay = 1)
}
- return packageChangeRepository
- .packageChanged(UserHandle.of(userId))
- .onStart { emit(PackageChangeModel.Empty) }
- .map { reloadComponents(userId, packageManager) }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
- }
+ }
@WorkerThread
private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
new file mode 100644
index 000000000000..2cebbe3f372c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * [A11yShortcutAutoAddable] will auto add/remove qs tile of the accessibility framework feature
+ * based on the user's choices in the Settings app.
+ *
+ * The a11y feature component is added to [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * selects to use qs tile as a shortcut for the a11 feature in the Settings app. The accessibility
+ * feature component is removed from [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * doesn't want to use qs tile as a shortcut for the a11y feature in the Settings app.
+ *
+ * [A11yShortcutAutoAddable] tracks a [Settings.Secure.ACCESSIBILITY_QS_TARGETS] and when its value
+ * changes, it will emit a [AutoAddSignal.Add] for the [spec] if the [componentName] is a substring
+ * of the value; it will emit a [AutoAddSignal.Remove] for the [spec] if the [componentName] is not
+ * a substring of the value.
+ */
+class A11yShortcutAutoAddable
+@AssistedInject
+constructor(
+ private val a11yQsShortcutsRepository: AccessibilityQsShortcutsRepository,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ @Assisted private val spec: TileSpec,
+ @Assisted private val componentName: ComponentName
+) : AutoAddable {
+
+ override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+ return a11yQsShortcutsRepository
+ .a11yQsShortcutTargets(userId)
+ .map { it.contains(componentName.flattenToString()) }
+ .filterNotNull()
+ .distinctUntilChanged()
+ .map { if (it) AutoAddSignal.Add(spec) else AutoAddSignal.Remove(spec) }
+ .flowOn(bgDispatcher)
+ }
+
+ override val autoAddTracking = AutoAddTracking.Always
+
+ override val description =
+ "A11yShortcutAutoAddableSetting: $spec:$componentName ($autoAddTracking)"
+
+ override fun equals(other: Any?): Boolean {
+ return other is A11yShortcutAutoAddable &&
+ spec == other.spec &&
+ componentName == other.componentName
+ }
+
+ override fun hashCode(): Int {
+ return Objects.hash(spec, componentName)
+ }
+
+ override fun toString(): String {
+ return description
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(spec: TileSpec, componentName: ComponentName): A11yShortcutAutoAddable
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
new file mode 100644
index 000000000000..08e39204386e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.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.qs.pipeline.domain.autoaddable
+
+import android.view.accessibility.Flags
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+
+object A11yShortcutAutoAddableList {
+
+ /**
+ * Generate a collection of [A11yShortcutAutoAddable] for the framework tiles related to
+ * accessibility features with shortcut options
+ */
+ fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> {
+ return if (Flags.a11yQsShortcut()) {
+ setOf(
+ factory.create(
+ TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+ AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ColorInversionTile.TILE_SPEC),
+ AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(OneHandedModeTile.TILE_SPEC),
+ AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+ ),
+ factory.create(
+ TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+ AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+ ),
+ )
+ } else {
+ emptySet()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index d04e4f52ac4d..53f287b81be9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -33,11 +33,13 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QSIconView;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.res.R;
import java.util.Objects;
@@ -52,7 +54,10 @@ public class QSIconViewImpl extends QSIconView {
private boolean mDisabledByPolicy = false;
private int mTint;
@Nullable
- private QSTile.Icon mLastIcon;
+ @VisibleForTesting
+ QSTile.Icon mLastIcon;
+
+ private boolean mIconChangeScheduled;
private ValueAnimator mColorAnimator = new ValueAnimator();
@@ -112,6 +117,7 @@ public class QSIconViewImpl extends QSIconView {
}
protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
+ mIconChangeScheduled = false;
final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))) {
boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
@@ -167,7 +173,12 @@ public class QSIconViewImpl extends QSIconView {
mState = state.state;
mDisabledByPolicy = state.disabledByPolicy;
if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
- animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
+ mIconChangeScheduled = true;
+ animateGrayScale(mTint, color, iv, () -> {
+ if (mIconChangeScheduled) {
+ updateIcon(iv, state, allowAnimations);
+ }
+ });
} else {
setTint(iv, color);
updateIcon(iv, state, allowAnimations);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 216d716b07a4..88863cbad1ee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -17,6 +17,8 @@
package com.android.systemui.qs.tiles
import android.app.AlertDialog
+import android.app.BroadcastOptions
+import android.app.PendingIntent
import android.content.Intent
import android.os.Handler
import android.os.Looper
@@ -42,6 +44,8 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
+import com.android.systemui.screenrecord.RecordingService
+import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -61,6 +65,7 @@ constructor(
private val keyguardDismissUtil: KeyguardDismissUtil,
private val keyguardStateController: KeyguardStateController,
private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val userContextProvider: UserContextProvider,
private val delegateFactory: RecordIssueDialogDelegate.Factory,
) :
QSTileImpl<QSTile.BooleanState>(
@@ -91,12 +96,22 @@ constructor(
public override fun handleClick(view: View?) {
if (isRecording) {
isRecording = false
+ stopScreenRecord()
} else {
mUiHandler.post { showPrompt(view) }
}
refreshState()
}
+ private fun stopScreenRecord() =
+ PendingIntent.getService(
+ userContextProvider.userContext,
+ RecordingService.REQUEST_CODE,
+ RecordingService.getStopIntent(userContextProvider.userContext),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
private fun showPrompt(view: View?) {
val dialog: AlertDialog =
delegateFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 592cb3b18e80..211b459471de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -192,6 +192,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
private DialogLaunchAnimator mDialogLaunchAnimator;
private boolean mHasWifiEntries;
private WifiStateWorker mWifiStateWorker;
+ private boolean mHasActiveSubId;
@VisibleForTesting
static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
@@ -299,6 +300,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
mExecutor);
// Listen the subscription changes
mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+ refreshHasActiveSubId();
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mOnSubscriptionsChangedListener);
mDefaultDataSubId = getDefaultDataSubscriptionId();
@@ -901,18 +903,22 @@ public class InternetDialogController implements AccessPointController.AccessPoi
* @return whether there is the carrier item in the slice.
*/
boolean hasActiveSubId() {
- if (mSubscriptionManager == null) {
- if (DEBUG) {
- Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
- }
+ if (isAirplaneModeEnabled() || mTelephonyManager == null) {
return false;
}
- if (isAirplaneModeEnabled() || mTelephonyManager == null
- || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
- return false;
+ return mHasActiveSubId;
+ }
+
+ private void refreshHasActiveSubId() {
+ if (mSubscriptionManager == null) {
+ mHasActiveSubId = false;
+ Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false");
+ return;
}
- return true;
+
+ mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0;
+ Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId);
}
/**
@@ -1204,6 +1210,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
@Override
public void onSubscriptionsChanged() {
+ refreshHasActiveSubId();
updateListener();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index cc53aabfcd25..f173900caa8c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,6 +16,7 @@
package com.android.systemui.recents;
+import static android.app.Flags.keyguardPrivateNotifications;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -81,12 +82,13 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.util.ScreenshotRequest;
import com.android.systemui.Dumpable;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.model.SysUiState;
@@ -167,9 +169,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final Optional<UnfoldTransitionProgressForwarder> mUnfoldTransitionProgressForwarder;
private final UiEventLogger mUiEventLogger;
private final DisplayTracker mDisplayTracker;
-
private Region mActiveNavBarRegion;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
private boolean mBound;
@@ -419,6 +422,21 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
retryConnectionWithBackoff();
};
+ private final BroadcastReceiver mUserEventReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Objects.equals(intent.getAction(), Intent.ACTION_USER_UNLOCKED)) {
+ if (keyguardPrivateNotifications()) {
+ // Start the overview connection to the launcher service
+ // Connect if user hasn't connected yet
+ if (getProxy() == null) {
+ startConnectionToCurrentUser();
+ }
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -586,7 +604,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
FeatureFlags featureFlags,
SceneContainerFlags sceneContainerFlags,
DumpManager dumpManager,
- Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder
+ Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
+ BroadcastDispatcher broadcastDispatcher
) {
// b/241601880: This component shouldn't be running for a non-primary user
if (!Process.myUserHandle().equals(UserHandle.SYSTEM)) {
@@ -615,8 +634,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
mUiEventLogger = uiEventLogger;
mDisplayTracker = displayTracker;
mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
+ mBroadcastDispatcher = broadcastDispatcher;
- if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (!KeyguardWmStateRefactor.isEnabled()) {
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
} else {
mSysuiUnlockAnimationController = inWindowLauncherUnlockAnimationManager;
@@ -635,6 +655,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
mContext.registerReceiver(mLauncherStateChangedReceiver, filter);
+ if (keyguardPrivateNotifications()) {
+ mBroadcastDispatcher.registerReceiver(mUserEventReceiver,
+ new IntentFilter(Intent.ACTION_USER_UNLOCKED),
+ null /* executor */, UserHandle.ALL);
+ }
+
// Listen for status bar state changes
statusBarWinController.registerCallback(mStatusBarWindowCallback);
mScreenshotHelper = new ScreenshotHelper(context);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index e2959fe834d4..1c37908235bd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -23,16 +23,22 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
+import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -47,6 +53,8 @@ constructor(
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ sceneContainerFlags: SceneContainerFlags,
+ sceneInteractorProvider: Provider<SceneInteractor>,
) : CoreStartable {
private var notificationPresenter: NotificationPresenter? = null
@@ -58,11 +66,28 @@ constructor(
/**
* True if lockscreen (including AOD) or the shade is visible and false otherwise. Notably,
* false if the bouncer is visible.
- *
- * TODO(b/297080059): Use [SceneInteractor] as the source of truth if the scene flag is on.
*/
val isLockscreenOrShadeVisible: StateFlow<Boolean> =
- windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+ if (!sceneContainerFlags.isEnabled()) {
+ windowRootViewVisibilityRepository.isLockscreenOrShadeVisible
+ } else {
+ sceneInteractorProvider
+ .get()
+ .transitionState
+ .map { state ->
+ when (state) {
+ is ObservableTransitionState.Idle ->
+ state.scene == SceneKey.Shade || state.scene == SceneKey.Lockscreen
+ is ObservableTransitionState.Transition ->
+ state.toScene == SceneKey.Shade ||
+ state.toScene == SceneKey.Lockscreen ||
+ state.fromScene == SceneKey.Shade ||
+ state.fromScene == SceneKey.Lockscreen
+ }
+ }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, false)
+ }
/**
* True if lockscreen (including AOD) or the shade is visible **and** the user is currently
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index c96651c1057e..995059dfaa9f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -313,7 +313,7 @@ constructor(
}
applicationScope.launch {
- keyguardInteractor.isDozing.distinctUntilChanged().collect { isDozing ->
+ keyguardInteractor.isDozing.collect { isDozing ->
falsingCollector.setShowingAod(isDozing)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.kt
new file mode 100644
index 000000000000..f71a401d1612
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/BaseShadeControllerImpl.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.shade
+
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+/** A base class for non-empty implementations of ShadeController. */
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class BaseShadeControllerImpl(
+ private val touchLog: LogBuffer,
+ protected val commandQueue: CommandQueue,
+ protected val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ protected val notificationShadeWindowController: NotificationShadeWindowController,
+ protected val assistManagerLazy: Lazy<AssistManager>
+) : ShadeController {
+ protected lateinit var notifPresenter: NotificationPresenter
+ /** Runnables to run after completing a collapse of the shade. */
+ private val postCollapseActions = ArrayList<Runnable>()
+
+ override fun start() {
+ logTouchesTo(touchLog)
+ }
+
+ final override fun animateExpandShade() {
+ if (isShadeEnabled) {
+ expandToNotifications()
+ }
+ }
+
+ /** Expand the shade with notifications visible. */
+ protected abstract fun expandToNotifications()
+
+ final override fun animateExpandQs() {
+ if (isShadeEnabled) {
+ expandToQs()
+ }
+ }
+
+ /** Expand the shade showing only quick settings. */
+ protected abstract fun expandToQs()
+
+ final override fun addPostCollapseAction(action: Runnable) {
+ postCollapseActions.add(action)
+ }
+
+ protected fun runPostCollapseActions() {
+ val clonedList: ArrayList<Runnable> = ArrayList(postCollapseActions)
+ postCollapseActions.clear()
+ for (r in clonedList) {
+ r.run()
+ }
+ statusBarKeyguardViewManager.readyForKeyguardDone()
+ }
+
+ final override fun onLaunchAnimationEnd(launchIsFullScreen: Boolean) {
+ if (!this.notifPresenter.isCollapsing()) {
+ onClosingFinished()
+ }
+ if (launchIsFullScreen) {
+ instantCollapseShade()
+ }
+ }
+ final override fun onLaunchAnimationCancelled(isLaunchForActivity: Boolean) {
+ if (
+ notifPresenter.isPresenterFullyCollapsed() &&
+ !notifPresenter.isCollapsing() &&
+ isLaunchForActivity
+ ) {
+ onClosingFinished()
+ } else {
+ collapseShade(true /* animate */)
+ }
+ }
+
+ protected fun onClosingFinished() {
+ runPostCollapseActions()
+ if (!this.notifPresenter.isPresenterFullyCollapsed()) {
+ // if we set it not to be focusable when collapsing, we have to undo it when we aborted
+ // the closing
+ notificationShadeWindowController.setNotificationShadeFocusable(true)
+ }
+ }
+
+ override fun setNotificationPresenter(presenter: NotificationPresenter) {
+ notifPresenter = presenter
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 44cb0d6ff2ba..97ec3f98cf0c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -22,6 +22,7 @@ import android.os.SystemClock
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
+import android.view.ViewGroup
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@@ -33,6 +34,7 @@ import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.util.kotlin.collectFlow
import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -49,7 +51,7 @@ constructor(
private val powerManager: PowerManager,
) {
/** The container view for the hub. This will not be initialized until [initView] is called. */
- private lateinit var communalContainerView: View
+ private var communalContainerView: View? = null
/**
* The width of the area in which a right edge swipe can open the hub, in pixels. Read from
@@ -108,6 +110,11 @@ constructor(
return communalInteractor.isCommunalEnabled && isComposeAvailable()
}
+ /** Returns a {@link StateFlow} that tracks whether communal hub is enabled. */
+ fun enabledState(): StateFlow<Boolean> {
+ return communalInteractor.communalEnabledState
+ }
+
/**
* Creates the container view containing the glanceable hub UI.
*
@@ -125,42 +132,44 @@ constructor(
if (!isEnabled()) {
throw RuntimeException("Glanceable hub is not enabled")
}
- if (::communalContainerView.isInitialized) {
+ if (communalContainerView != null) {
throw RuntimeException("Communal view has already been initialized")
}
communalContainerView = containerView
rightEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_right_edge_swipe_region_width
)
topEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_top_edge_swipe_region_height
)
bottomEdgeSwipeRegionWidth =
- communalContainerView.resources.getDimensionPixelSize(
+ containerView.resources.getDimensionPixelSize(
R.dimen.communal_bottom_edge_swipe_region_height
)
collectFlow(
- communalContainerView,
+ containerView,
keyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState),
{ anyBouncerShowing = it }
)
- collectFlow(
- communalContainerView,
- communalInteractor.isCommunalShowing,
- { hubShowing = it }
- )
- collectFlow(
- communalContainerView,
- shadeInteractor.isAnyFullyExpanded,
- { shadeShowing = it }
- )
+ collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it })
+ collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it })
+
+ communalContainerView = containerView
+
+ return containerView
+ }
- return communalContainerView
+ /** Removes the container view from its parent. */
+ fun disposeView() {
+ communalContainerView?.let {
+ (it.parent as ViewGroup).removeView(it)
+ communalContainerView = null
+ }
}
/**
@@ -173,10 +182,10 @@ constructor(
* to be fully in control of its own touch handling.
*/
fun onTouchEvent(ev: MotionEvent): Boolean {
- if (!::communalContainerView.isInitialized) {
- return false
- }
+ return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
+ }
+ private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
val isUp = ev.actionMasked == MotionEvent.ACTION_UP
val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
@@ -190,7 +199,7 @@ constructor(
if (hubShowing && isDown) {
val y = ev.rawY
val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth
- val bottomSwipe = y >= communalContainerView.height - bottomEdgeSwipeRegionWidth
+ val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth
if (topSwipe || bottomSwipe) {
// Don't intercept touches at the top/bottom edge so that swipes can open the
@@ -200,7 +209,7 @@ constructor(
if (!hubOccluded) {
isTrackingHubTouch = true
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a
// gesture may return false from dispatchTouchEvent.
return true
@@ -209,7 +218,7 @@ constructor(
if (isUp || isCancel) {
isTrackingHubTouch = false
}
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -223,11 +232,10 @@ constructor(
if (!isTrackingOpenGesture && isDown) {
val x = ev.rawX
- val inOpeningSwipeRegion: Boolean =
- x >= communalContainerView.width - rightEdgeSwipeRegionWidth
+ val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
if (inOpeningSwipeRegion && !hubOccluded) {
isTrackingOpenGesture = true
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a
// gesture may return false from dispatchTouchEvent.
return true
@@ -236,7 +244,7 @@ constructor(
if (isUp || isCancel) {
isTrackingOpenGesture = false
}
- dispatchTouchEvent(ev)
+ dispatchTouchEvent(view, ev)
// Return true regardless of dispatch result as some touches at the start of a gesture
// may return false from dispatchTouchEvent.
return true
@@ -249,8 +257,8 @@ constructor(
* Dispatches the touch event to the communal container and sends a user activity event to reset
* the screen timeout.
*/
- private fun dispatchTouchEvent(ev: MotionEvent) {
- communalContainerView.dispatchTouchEvent(ev)
+ private fun dispatchTouchEvent(view: View, ev: MotionEvent) {
+ view.dispatchTouchEvent(ev)
powerManager.userActivity(
SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index aeccf0031419..530c124f4f24 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2883,7 +2883,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void onTrackingStarted() {
endClosing();
mShadeRepository.setLegacyShadeTracking(true);
- mTrackingStartedListener.onTrackingStarted();
+ if (mTrackingStartedListener != null) {
+ mTrackingStartedListener.onTrackingStarted();
+ }
notifyExpandingStarted();
updateExpansionAndVisibility();
mScrimController.onTrackingStarted();
@@ -3574,11 +3576,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
@Override
- public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
- return mNotificationStackScrollLayoutController;
- }
-
- @Override
public void disableHeader(int state1, int state2, boolean animated) {
mShadeHeaderController.disable(state1, state2, animated);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 863bb36e4ed0..5ecc54b09806 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -605,16 +605,21 @@ public class NotificationShadeWindowViewController implements Dumpable {
* The layout lives in {@link R.id.communal_ui_stub}.
*/
public void setupCommunalHubLayout() {
- if (!mGlanceableHubContainerController.isEnabled()) {
- return;
- }
-
- // Replace the placeholder view with the communal UI.
- View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
- int index = mView.indexOfChild(communalPlaceholder);
- mView.removeView(communalPlaceholder);
-
- mView.addView(mGlanceableHubContainerController.initView(mView.getContext()), index);
+ collectFlow(
+ mView,
+ mGlanceableHubContainerController.enabledState(),
+ isEnabled -> {
+ if (isEnabled) {
+ View communalPlaceholder = mView.findViewById(R.id.communal_ui_stub);
+ int index = mView.indexOfChild(communalPlaceholder);
+ mView.addView(
+ mGlanceableHubContainerController.initView(mView.getContext()),
+ index);
+ } else {
+ mGlanceableHubContainerController.disposeView();
+ }
+ }
+ );
}
private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 2c4b0b990e6d..ec4b23a56483 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -33,10 +33,20 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
* {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
*/
public interface ShadeController extends CoreStartable {
- /** True if the shade UI is enabled on this particular Android variant and false otherwise. */
+ /**
+ * True if the shade UI is enabled on this particular Android variant and false otherwise.
+ *
+ * @deprecated use ShadeInteractor instead
+ */
+ @Deprecated
boolean isShadeEnabled();
- /** Make our window larger and the shade expanded */
+ /**
+ * Make our window larger and the shade expanded
+ *
+ * @deprecated will no longer be needed when keyguard is a sibling view to the shade
+ */
+ @Deprecated
void instantExpandShade();
/** Collapse the shade instantly with no animation. */
@@ -74,13 +84,28 @@ public interface ShadeController extends CoreStartable {
/** Expand the shade with quick settings expanded with an animation. */
void animateExpandQs();
- /** Posts a request to collapse the shade. */
+ /**
+ * Posts a request to collapse the shade.
+ *
+ * @deprecated use #animateCollapseShade
+ */
+ @Deprecated
void postAnimateCollapseShade();
- /** Posts a request to force collapse the shade. */
+ /**
+ * Posts a request to force collapse the shade.
+ *
+ * @deprecated use #animateForceCollapseShade
+ */
+ @Deprecated
void postAnimateForceCollapseShade();
- /** Posts a request to expand the shade to quick settings. */
+ /**
+ * Posts a request to expand the shade to quick settings.
+ *
+ * @deprecated use #animateExpandQs
+ */
+ @Deprecated
void postAnimateExpandQs();
/** Cancels any ongoing expansion touch handling and collapses the shade. */
@@ -90,26 +115,29 @@ public interface ShadeController extends CoreStartable {
* If the shade is not fully expanded, collapse it animated.
*
* @return Seems to always return false
+ * @deprecated use {@link #collapseShade()} instead
*/
+ @Deprecated
boolean closeShadeIfOpen();
/**
- * Returns whether the shade state is the keyguard or not.
- */
- boolean isKeyguard();
-
- /**
* Returns whether the shade is currently open.
* Even though in the current implementation shade is in expanded state on keyguard, this
* method makes distinction between shade being truly open and plain keyguard state:
* - if QS and notifications are visible on the screen, return true
* - for any other state, including keyguard, return false
+ *
+ * @deprecated will be replaced by ShadeInteractor once scene container launches
*/
+ @Deprecated
boolean isShadeFullyOpen();
/**
* Returns whether shade or QS are currently opening or collapsing.
+ *
+ * @deprecated will be replaced by ShadeInteractor once scene container launches
*/
+ @Deprecated
boolean isExpandingOrCollapsing();
/**
@@ -127,37 +155,67 @@ public interface ShadeController extends CoreStartable {
*/
void addPostCollapseAction(Runnable action);
- /** Run all of the runnables added by {@link #addPostCollapseAction}. */
- void runPostCollapseRunnables();
-
/**
* Close the shade if it was open
*
* @return true if the shade was open, else false
*/
- boolean collapseShade();
+ void collapseShade();
/**
* If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
* the shade. Post collapse runnables will be executed
*
* @param animate true to animate the collapse, false for instantaneous collapse
+ * @deprecated call either #animateCollapseShade or #instantCollapseShade
*/
+ @Deprecated
void collapseShade(boolean animate);
- /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+ /**
+ * Calls #collapseShade if already on the main thread. If not, posts a call to it.
+ * @deprecated call #collapseShade
+ */
+ @Deprecated
void collapseOnMainThread();
- /** Makes shade expanded but not visible. */
+ /**
+ * If necessary, instantly collapses the shade for an activity start, otherwise runs the
+ * post-collapse runnables. Instant collapse is ok here, because the purpose is to have the
+ * shade collapsed when the user returns to SysUI from the launched activity.
+ */
+ void collapseShadeForActivityStart();
+
+ /**
+ * Makes shade expanded but not visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
void makeExpandedInvisible();
- /** Makes shade expanded and visible. */
+ /**
+ * Makes shade expanded and visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
void makeExpandedVisible(boolean force);
- /** Returns whether the shade is expanded and visible. */
+ /**
+ * Returns whether the shade is expanded and visible.
+ *
+ * @deprecated no longer needed once keyguard is a sibling view to the shade
+ */
+ @Deprecated
boolean isExpandedVisible();
- /** Handle status bar touch event. */
+ /**
+ * Handle status bar touch event.
+ *
+ * @deprecated only called by CentralSurfaces, which is being deleted
+ */
+ @Deprecated
void onStatusBarTouch(MotionEvent event);
/** Called when a launch animation was cancelled. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 82959eea562b..08a0c934702d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -42,9 +42,6 @@ open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
override fun closeShadeIfOpen(): Boolean {
return false
}
- override fun isKeyguard(): Boolean {
- return false
- }
override fun isShadeFullyOpen(): Boolean {
return false
}
@@ -53,12 +50,10 @@ open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
}
override fun postOnShadeExpanded(action: Runnable?) {}
override fun addPostCollapseAction(action: Runnable?) {}
- override fun runPostCollapseRunnables() {}
- override fun collapseShade(): Boolean {
- return false
- }
+ override fun collapseShade() {}
override fun collapseShade(animate: Boolean) {}
override fun collapseOnMainThread() {}
+ override fun collapseShadeForActivityStart() {}
override fun makeExpandedInvisible() {}
override fun makeExpandedVisible(force: Boolean) {}
override fun isExpandedVisible(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index fdc7eecd9b15..e8d9c35a893b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -33,7 +33,6 @@ import com.android.systemui.log.dagger.ShadeTouchLog;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -44,14 +43,13 @@ import com.android.systemui.statusbar.window.StatusBarWindowController;
import dagger.Lazy;
-import java.util.ArrayList;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/** An implementation of {@link ShadeController}. */
@SysUISingleton
-public final class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl extends BaseShadeControllerImpl {
private static final String TAG = "ShadeControllerImpl";
private static final boolean SPEW = false;
@@ -60,7 +58,6 @@ public final class ShadeControllerImpl implements ShadeController {
private final CommandQueue mCommandQueue;
private final Executor mMainExecutor;
- private final LogBuffer mTouchLog;
private final WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -73,12 +70,9 @@ public final class ShadeControllerImpl implements ShadeController {
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<NotificationGutsManager> mGutsManager;
- private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
-
private boolean mExpandedVisible;
private boolean mLockscreenOrShadeVisible;
- private NotificationPresenter mPresenter;
private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private ShadeVisibilityListener mShadeVisibilityListener;
@@ -99,9 +93,13 @@ public final class ShadeControllerImpl implements ShadeController {
Lazy<AssistManager> assistManagerLazy,
Lazy<NotificationGutsManager> gutsManager
) {
+ super(touchLog,
+ commandQueue,
+ statusBarKeyguardViewManager,
+ notificationShadeWindowController,
+ assistManagerLazy);
mCommandQueue = commandQueue;
mMainExecutor = mainExecutor;
- mTouchLog = touchLog;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mShadeViewControllerLazy = shadeViewControllerLazy;
mStatusBarStateController = statusBarStateController;
@@ -117,7 +115,7 @@ public final class ShadeControllerImpl implements ShadeController {
@Override
public boolean isShadeEnabled() {
- return true;
+ return mCommandQueue.panelsEnabled() && mDeviceProvisionedController.isCurrentUserSetup();
}
@Override
@@ -125,20 +123,16 @@ public final class ShadeControllerImpl implements ShadeController {
// Make our window larger and the panel expanded.
makeExpandedVisible(true /* force */);
getShadeViewController().expand(false /* animate */);
- mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
+ getCommandQueue().recomputeDisableFlags(mDisplayId, false /* animate */);
}
@Override
public void animateCollapseShade(int flags, boolean force, boolean delayed,
float speedUpFactor) {
if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
- runPostCollapseRunnables();
+ runPostCollapseActions();
return;
}
- if (SPEW) {
- Log.d(TAG,
- "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
- }
if (getNotificationShadeWindowView() != null
&& getShadeViewController().canBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
@@ -151,28 +145,19 @@ public final class ShadeControllerImpl implements ShadeController {
}
@Override
- public void animateExpandShade() {
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
+ protected void expandToNotifications() {
getShadeViewController().expandToNotifications();
}
@Override
- public void animateExpandQs() {
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
- // Settings are not available in setup
- if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
+ protected void expandToQs() {
getShadeViewController().expandToQs();
}
@Override
public boolean closeShadeIfOpen() {
if (!getShadeViewController().isFullyCollapsed()) {
- mCommandQueue.animateCollapsePanels(
+ getCommandQueue().animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
notifyVisibilityChanged(false);
mAssistManagerLazy.get().hideAssist();
@@ -181,11 +166,6 @@ public final class ShadeControllerImpl implements ShadeController {
}
@Override
- public boolean isKeyguard() {
- return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
- }
-
- @Override
public boolean isShadeFullyOpen() {
return getShadeViewController().isShadeFullyExpanded();
}
@@ -224,46 +204,34 @@ public final class ShadeControllerImpl implements ShadeController {
}
@Override
- public void addPostCollapseAction(Runnable action) {
- mPostCollapseRunnables.add(action);
+ public void collapseShade() {
+ collapseShadeInternal();
}
- @Override
- public void runPostCollapseRunnables() {
- ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables);
- mPostCollapseRunnables.clear();
- int size = clonedList.size();
- for (int i = 0; i < size; i++) {
- clonedList.get(i).run();
- }
- mStatusBarKeyguardViewManager.readyForKeyguardDone();
- }
-
- @Override
- public boolean collapseShade() {
+ private boolean collapseShadeInternal() {
if (!getShadeViewController().isFullyCollapsed()) {
// close the shade if it was open
animateCollapseShadeForcedDelayed();
notifyVisibilityChanged(false);
-
return true;
} else {
return false;
}
}
+
@Override
public void collapseShade(boolean animate) {
if (animate) {
- boolean willCollapse = collapseShade();
+ boolean willCollapse = collapseShadeInternal();
if (!willCollapse) {
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
- } else if (!mPresenter.isPresenterFullyCollapsed()) {
+ } else if (!getNotifPresenter().isPresenterFullyCollapsed()) {
instantCollapseShade();
notifyVisibilityChanged(false);
} else {
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
}
@@ -296,46 +264,16 @@ public final class ShadeControllerImpl implements ShadeController {
}
}
- private void onClosingFinished() {
- runPostCollapseRunnables();
- if (!mPresenter.isPresenterFullyCollapsed()) {
- // if we set it not to be focusable when collapsing, we have to undo it when we aborted
- // the closing
- mNotificationShadeWindowController.setNotificationShadeFocusable(true);
- }
- }
-
- @Override
- public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
- if (mPresenter.isPresenterFullyCollapsed()
- && !mPresenter.isCollapsing()
- && isLaunchForActivity) {
- onClosingFinished();
- } else {
- collapseShade(true /* animate */);
- }
- }
-
- @Override
- public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
- if (!mPresenter.isCollapsing()) {
- onClosingFinished();
- }
- if (launchIsFullScreen) {
- instantCollapseShade();
- }
- }
-
@Override
public void instantCollapseShade() {
getShadeViewController().instantCollapse();
- runPostCollapseRunnables();
+ runPostCollapseActions();
}
@Override
public void makeExpandedVisible(boolean force) {
if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
- if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+ if (!force && (mExpandedVisible || !getCommandQueue().panelsEnabled())) {
return;
}
@@ -346,7 +284,7 @@ public final class ShadeControllerImpl implements ShadeController {
mNotificationShadeWindowController.setPanelVisible(true);
notifyVisibilityChanged(true);
- mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+ getCommandQueue().recomputeDisableFlags(mDisplayId, !force /* animate */);
notifyExpandedVisibleChanged(true);
}
@@ -377,9 +315,9 @@ public final class ShadeControllerImpl implements ShadeController {
-1 /* y */,
true /* resetMenu */);
- runPostCollapseRunnables();
+ runPostCollapseActions();
notifyExpandedVisibleChanged(false);
- mCommandQueue.recomputeDisableFlags(
+ getCommandQueue().recomputeDisableFlags(
mDisplayId,
getShadeViewController().shouldHideStatusBarIconsWhenExpanded());
@@ -421,11 +359,6 @@ public final class ShadeControllerImpl implements ShadeController {
}
@Override
- public void setNotificationPresenter(NotificationPresenter presenter) {
- mPresenter = presenter;
- }
-
- @Override
public void setNotificationShadeWindowViewController(
NotificationShadeWindowViewController controller) {
mNotificationShadeWindowViewController = controller;
@@ -441,8 +374,8 @@ public final class ShadeControllerImpl implements ShadeController {
@Override
public void start() {
- TouchLogger.logTouchesTo(mTouchLog);
- getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
+ super.start();
+ getShadeViewController().setTrackingStartedListener(this::runPostCollapseActions);
getShadeViewController().setOpenCloseListener(
new OpenCloseListener() {
@Override
@@ -456,4 +389,16 @@ public final class ShadeControllerImpl implements ShadeController {
}
});
}
+
+ @Override
+ public void collapseShadeForActivityStart() {
+ if (isExpandedVisible() && !mStatusBarKeyguardViewManager.isBouncerShowing()) {
+ animateCollapseShadeForcedDelayed();
+ } else {
+ // Do it after DismissAction has been processed to conserve the
+ // needed ordering.
+ mMainExecutor.execute(this::runPostCollapseActions);
+ }
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
new file mode 100644
index 000000000000..10b9db0a349b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.MotionEvent
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.dagger.ShadeTouchLog
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+
+/**
+ * Implementation of ShadeController backed by scenes instead of NPVC.
+ *
+ * TODO(b/300258424) rename to ShadeControllerImpl and inline/delete all the deprecated methods
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class ShadeControllerSceneImpl
+@Inject
+constructor(
+ @Background private val scope: CoroutineScope,
+ private val shadeInteractor: ShadeInteractor,
+ private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val notificationStackScrollLayout: NotificationStackScrollLayout,
+ @ShadeTouchLog private val touchLog: LogBuffer,
+ commandQueue: CommandQueue,
+ statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ notificationShadeWindowController: NotificationShadeWindowController,
+ assistManagerLazy: Lazy<AssistManager>,
+) :
+ BaseShadeControllerImpl(
+ touchLog,
+ commandQueue,
+ statusBarKeyguardViewManager,
+ notificationShadeWindowController,
+ assistManagerLazy,
+ ) {
+
+ init {
+ scope.launch {
+ shadeInteractor.isAnyExpanded.collect {
+ if (!it) {
+ runPostCollapseActions()
+ }
+ }
+ }
+ }
+
+ override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value
+
+ override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
+
+ override fun isExpandingOrCollapsing(): Boolean =
+ shadeInteractor.anyExpansion.value > 0f && shadeInteractor.anyExpansion.value < 1f
+
+ override fun instantExpandShade() {
+ // Do nothing
+ }
+
+ override fun instantCollapseShade() {
+ // TODO(b/315921512) add support for instant transition
+ sceneInteractor.changeScene(
+ SceneModel(getCollapseDestinationScene(), "instant"),
+ "hide shade"
+ )
+ }
+
+ override fun animateCollapseShade(
+ flags: Int,
+ force: Boolean,
+ delayed: Boolean,
+ speedUpFactor: Float
+ ) {
+ if (!force && !shadeInteractor.isAnyExpanded.value) {
+ runPostCollapseActions()
+ return
+ }
+ if (
+ shadeInteractor.isAnyExpanded.value &&
+ flags and CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL == 0
+ ) {
+ // release focus immediately to kick off focus change transition
+ notificationShadeWindowController.setNotificationShadeFocusable(false)
+ notificationStackScrollLayout.cancelExpandHelper()
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.Shade, null),
+ "ShadeController.animateExpandShade"
+ )
+ if (delayed) {
+ scope.launch {
+ delay(125)
+ animateCollapseShadeInternal()
+ }
+ } else {
+ animateCollapseShadeInternal()
+ }
+ }
+ }
+
+ private fun animateCollapseShadeInternal() {
+ sceneInteractor.changeScene(
+ SceneModel(getCollapseDestinationScene(), "ShadeController.animateCollapseShade"),
+ "ShadeController.animateCollapseShade"
+ )
+ }
+
+ private fun getCollapseDestinationScene(): SceneKey {
+ return if (deviceEntryInteractor.isDeviceEntered.value) {
+ SceneKey.Gone
+ } else {
+ SceneKey.Lockscreen
+ }
+ }
+
+ override fun cancelExpansionAndCollapseShade() {
+ // TODO do we need to actually cancel the touch session?
+ animateCollapseShade()
+ }
+
+ override fun closeShadeIfOpen(): Boolean {
+ if (shadeInteractor.isAnyExpanded.value) {
+ commandQueue.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
+ true /* force */
+ )
+ assistManagerLazy.get().hideAssist()
+ }
+ return false
+ }
+
+ override fun collapseShade() {
+ animateCollapseShadeForcedDelayed()
+ }
+
+ override fun collapseShade(animate: Boolean) {
+ if (animate) {
+ animateCollapseShade()
+ } else {
+ instantCollapseShade()
+ }
+ }
+
+ override fun collapseOnMainThread() {
+ // TODO if this works with delegation alone, we can deprecate and delete
+ collapseShade()
+ }
+
+ override fun expandToNotifications() {
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.Shade, null),
+ "ShadeController.animateExpandShade"
+ )
+ }
+
+ override fun expandToQs() {
+ sceneInteractor.changeScene(
+ SceneModel(SceneKey.QuickSettings, null),
+ "ShadeController.animateExpandQs"
+ )
+ }
+
+ override fun setVisibilityListener(listener: ShadeVisibilityListener) {
+ scope.launch { sceneInteractor.isVisible.collect { listener.expandedVisibleChanged(it) } }
+ }
+
+ @ExperimentalCoroutinesApi
+ override fun collapseShadeForActivityStart() {
+ if (shadeInteractor.isAnyExpanded.value) {
+ animateCollapseShadeForcedDelayed()
+ } else {
+ runPostCollapseActions()
+ }
+ }
+
+ override fun postAnimateCollapseShade() {
+ animateCollapseShade()
+ }
+
+ override fun postAnimateForceCollapseShade() {
+ animateCollapseShadeForced()
+ }
+
+ override fun postAnimateExpandQs() {
+ expandToQs()
+ }
+
+ override fun postOnShadeExpanded(action: Runnable) {
+ // TODO verify that clicking "reply" in a work profile notification launches the app
+ // TODO verify that there's not a way to replace and deprecate this method
+ scope.launch {
+ shadeInteractor.isAnyFullyExpanded.first { it }
+ action.run()
+ }
+ }
+
+ override fun makeExpandedInvisible() {
+ // Do nothing
+ }
+
+ override fun makeExpandedVisible(force: Boolean) {
+ // Do nothing
+ }
+
+ override fun isExpandedVisible(): Boolean {
+ return sceneInteractor.desiredScene.value.key != SceneKey.Gone
+ }
+
+ override fun onStatusBarTouch(event: MotionEvent) {
+ // The only call to this doesn't happen with KeyguardShadeMigrationNssl enabled
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index c057147b022a..fc2c3ee14206 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -53,6 +53,20 @@ abstract class ShadeModule {
@Provides
@SysUISingleton
+ fun provideShadeController(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<ShadeControllerSceneImpl>,
+ sceneContainerOff: Provider<ShadeControllerImpl>
+ ): ShadeController {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
fun provideShadeAnimationInteractor(
sceneContainerFlags: SceneContainerFlags,
sceneContainerOn: Provider<ShadeAnimationInteractorSceneContainerImpl>,
@@ -79,8 +93,4 @@ abstract class ShadeModule {
abstract fun bindsShadeViewController(
notificationPanelViewController: NotificationPanelViewController
): ShadeViewController
-
- @Binds
- @SysUISingleton
- abstract fun bindsShadeController(shadeControllerImpl: ShadeControllerImpl): ShadeController
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index e54286fcf992..4f970b3923aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -17,7 +17,6 @@ package com.android.systemui.shade
import android.view.ViewPropertyAnimator
import com.android.systemui.statusbar.GestureRecorder
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.policy.HeadsUpManager
@@ -44,9 +43,6 @@ interface ShadeSurface : ShadeViewController {
/** Animates the view from its current alpha to zero then runs the runnable. */
fun fadeOut(startDelayMs: Long, durationMs: Long, endAction: Runnable): ViewPropertyAnimator
- /** Returns the NSSL controller. */
- val notificationStackScrollLayoutController: NotificationStackScrollLayoutController
-
/** Set whether the bouncer is showing. */
fun setBouncerShowing(bouncerShowing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 31a4de4d630e..43ede2aa288d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -29,7 +29,7 @@ interface ShadeInteractor : BaseShadeInteractor {
val isShadeEnabled: StateFlow<Boolean>
/** Whether either the shade or QS is fully expanded. */
- val isAnyFullyExpanded: Flow<Boolean>
+ val isAnyFullyExpanded: StateFlow<Boolean>
/** Whether the Shade is fully expanded. */
val isShadeFullyExpanded: Flow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index 6defbcf29a6c..55dd6744d44e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -34,7 +34,7 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor {
override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
override val isQsFullscreen: Flow<Boolean> = inactiveFlowBoolean
override val anyExpansion: StateFlow<Float> = inactiveFlowFloat
- override val isAnyFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
+ override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean
override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 6407b5a4d16c..a71cf950cbe1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -55,12 +55,20 @@ constructor(
private val baseShadeInteractor: BaseShadeInteractor,
) : ShadeInteractor, BaseShadeInteractor by baseShadeInteractor {
override val isShadeEnabled: StateFlow<Boolean> =
- disableFlagsRepository.disableFlags
- .map { it.isShadeEnabled() }
+ combine(
+ deviceProvisioningRepository.isFactoryResetProtectionActive,
+ disableFlagsRepository.disableFlags,
+ ) { isFrpActive, isDisabledByFlags ->
+ isDisabledByFlags.isShadeEnabled() && !isFrpActive
+ }
+ .distinctUntilChanged()
.stateIn(scope, SharingStarted.Eagerly, initialValue = false)
- override val isAnyFullyExpanded: Flow<Boolean> =
- anyExpansion.map { it >= 1f }.distinctUntilChanged()
+ override val isAnyFullyExpanded: StateFlow<Boolean> =
+ anyExpansion
+ .map { it >= 1f }
+ .distinctUntilChanged()
+ .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
override val isShadeFullyExpanded: Flow<Boolean> =
baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 24ac70e63e46..2a4753def463 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -234,10 +234,12 @@ public class NotificationLockscreenUserManagerImpl implements
} else if (profileAvailabilityActions(action)) {
updateCurrentProfilesCache();
} else if (Objects.equals(action, Intent.ACTION_USER_UNLOCKED)) {
- // Start the overview connection to the launcher service
- // Connect if user hasn't connected yet
- if (mOverviewProxyServiceLazy.get().getProxy() == null) {
- mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ if (!keyguardPrivateNotifications()) {
+ // Start the overview connection to the launcher service
+ // Connect if user hasn't connected yet
+ if (mOverviewProxyServiceLazy.get().getProxy() == null) {
+ mOverviewProxyServiceLazy.get().startConnectionToCurrentUser();
+ }
}
} else if (Objects.equals(action, NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION)) {
final IntentSender intentSender = intent.getParcelableExtra(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index fc84973c46bd..28d4457b264b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -35,7 +35,6 @@ import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.Looper;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -61,7 +60,6 @@ import com.android.settingslib.mobile.MobileStatusTracker.SubscriptionDefaults;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -73,6 +71,7 @@ import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -85,6 +84,8 @@ import com.android.systemui.util.CarrierConfigTracker;
import dalvik.annotation.optimization.NeverCompile;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -99,8 +100,6 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
-import kotlin.Unit;
-
/** Platform implementation of the network controller. **/
@SysUISingleton
public class NetworkControllerImpl extends BroadcastReceiver
@@ -350,7 +349,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
// AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
updateAirplaneMode(true /* force callback */);
mUserTracker = userTracker;
- mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
+ mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
deviceProvisionedController.addCallback(new DeviceProvisionedListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 6e3b15da4423..c643238b7e30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -52,8 +52,8 @@ class ConversationNotificationProcessor @Inject constructor(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
logger: NotificationContentInflaterLogger
- ) {
- val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
+ ): Notification.MessagingStyle? {
+ val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
messagingStyle.conversationType =
if (entry.ranking.channel.isImportantConversation)
Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
@@ -68,6 +68,7 @@ class ConversationNotificationProcessor @Inject constructor(
}
messagingStyle.unreadMessageCount =
conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
+ return messagingStyle
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 73decfc326a4..639e23ae0765 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -362,8 +362,12 @@ public class PreparationCoordinator implements Coordinator {
}
NotifInflater.Params getInflaterParams(NotifUiAdjustment adjustment, String reason) {
- return new NotifInflater.Params(adjustment.isMinimized(), reason,
- adjustment.isSnoozeEnabled());
+ return new NotifInflater.Params(
+ adjustment.isMinimized(),
+ reason,
+ adjustment.isSnoozeEnabled(),
+ adjustment.isChildInGroup()
+ );
}
private void abortInflation(NotificationEntry entry, String reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index 4483599d6857..c0b187be42f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -20,9 +20,9 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.render.NotifViewController
/**
- * Used by the [PreparationCoordinator]. When notifications are added or updated, the
- * NotifInflater is asked to (re)inflated and prepare their views. This inflation occurs off the
- * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ * Used by the [PreparationCoordinator]. When notifications are added or updated, the NotifInflater
+ * is asked to (re)inflated and prepare their views. This inflation occurs off the main thread. When
+ * the inflation is finished, NotifInflater will trigger its InflationCallback.
*/
interface NotifInflater {
/**
@@ -33,7 +33,7 @@ interface NotifInflater {
fun rebindViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
/**
- * Called to inflate the views of an entry. Views are not considered inflated until all of its
+ * Called to inflate the views of an entry. Views are not considered inflated until all of its
* views are bound. Once all views are inflated, the InflationCallback is triggered.
*
* @param callback callback called after inflation finishes
@@ -41,25 +41,24 @@ interface NotifInflater {
fun inflateViews(entry: NotificationEntry, params: Params, callback: InflationCallback)
/**
- * Request to stop the inflation of an entry. For example, called when a notification is
- * removed and no longer needs to be inflated. Returns whether anything may have been aborted.
+ * Request to stop the inflation of an entry. For example, called when a notification is removed
+ * and no longer needs to be inflated. Returns whether anything may have been aborted.
*/
fun abortInflation(entry: NotificationEntry): Boolean
- /**
- * Called to let the system remove the content views from the notification row.
- */
+ /** Called to let the system remove the content views from the notification row. */
fun releaseViews(entry: NotificationEntry)
- /**
- * Callback once all the views are inflated and bound for a given NotificationEntry.
- */
+ /** Callback once all the views are inflated and bound for a given NotificationEntry. */
interface InflationCallback {
fun onInflationFinished(entry: NotificationEntry, controller: NotifViewController)
}
- /**
- * A class holding parameters used when inflating the notification row
- */
- class Params(val isLowPriority: Boolean, val reason: String, val showSnooze: Boolean)
+ /** A class holding parameters used when inflating the notification row */
+ class Params(
+ val isLowPriority: Boolean,
+ val reason: String,
+ val showSnooze: Boolean,
+ val isChildInGroup: Boolean = false,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index ee0b00807e27..e1d2cdc65d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -20,6 +20,7 @@ import android.app.Notification
import android.app.RemoteInput
import android.graphics.drawable.Icon
import android.text.TextUtils
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
/**
* An immutable object which contains minimal state extracted from an entry that represents state
@@ -34,6 +35,7 @@ class NotifUiAdjustment internal constructor(
val isSnoozeEnabled: Boolean,
val isMinimized: Boolean,
val needsRedaction: Boolean,
+ val isChildInGroup: Boolean,
) {
companion object {
@JvmStatic
@@ -48,6 +50,11 @@ class NotifUiAdjustment internal constructor(
oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
newAdjustment.smartReplies != oldAdjustment.smartReplies -> true
+ // TODO(b/217799515): Here we decide whether to re-inflate the row on every group-status
+ // change if we want to keep the single-line view, the following line should be:
+ // !oldAdjustment.isChildInGroup && newAdjustment.isChildInGroup -> true
+ AsyncHybridViewInflation.isEnabled &&
+ oldAdjustment.isChildInGroup != newAdjustment.isChildInGroup -> true
else -> false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index 058545689c01..6f44c13a3e71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -29,6 +29,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.util.ListenerSet
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
@@ -43,7 +44,8 @@ class NotifUiAdjustmentProvider @Inject constructor(
private val secureSettings: SecureSettings,
private val lockscreenUserManager: NotificationLockscreenUserManager,
private val sectionStyleProvider: SectionStyleProvider,
- private val userTracker: UserTracker
+ private val userTracker: UserTracker,
+ private val groupMembershipManager: GroupMembershipManager,
) {
private val dirtyListeners = ListenerSet<Runnable>()
private var isSnoozeSettingsEnabled = false
@@ -121,5 +123,6 @@ class NotifUiAdjustmentProvider @Inject constructor(
isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled,
isMinimized = isEntryMinimized(entry),
needsRedaction = lockscreenUserManager.needsRedaction(entry),
+ isChildInGroup = groupMembershipManager.isChildInGroup(entry),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 80ef14bb4673..cd816aea452b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -20,6 +20,7 @@ import static com.android.systemui.Flags.screenshareNotificationHiding;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE;
import static java.util.Objects.requireNonNull;
@@ -49,6 +50,7 @@ import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import javax.inject.Inject;
@@ -127,6 +129,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
@NonNull NotifInflater.Params params,
NotificationRowContentBinder.InflationCallback callback)
throws InflationException {
+ //TODO(b/217799515): Remove the entry parameter from getViewParentForNotification(), this
+ // function returns the NotificationStackScrollLayout regardless of the entry.
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
if (entry.rowExists()) {
@@ -174,6 +178,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+ }
mRowContentBindStage.requestRebind(entry, null);
}
@@ -254,6 +261,16 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
}
+ if (AsyncHybridViewInflation.isEnabled()) {
+ if (inflaterParams.isChildInGroup()) {
+ params.requireContentViews(FLAG_CONTENT_VIEW_SINGLE_LINE);
+ } else {
+ // TODO(b/217799515): here we decide whether to free the single-line view
+ // when the group status changes
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_SINGLE_LINE);
+ }
+ }
+
params.rebindAllContentViews();
mLogger.logRequestingRebind(entry, inflaterParams);
mRowContentBindStage.requestRebind(entry, en -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 2d5afd56da72..3cdb2cd9b5c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -21,8 +21,6 @@ import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -53,14 +51,11 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
*/
private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
- private final FeatureFlags mFeatureFlags;
-
@Inject
public GroupExpansionManagerImpl(DumpManager dumpManager,
- GroupMembershipManager groupMembershipManager, FeatureFlags featureFlags) {
+ GroupMembershipManager groupMembershipManager) {
mDumpManager = dumpManager;
mGroupMembershipManager = groupMembershipManager;
- mFeatureFlags = featureFlags;
}
/**
@@ -86,10 +81,8 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
};
public void attach(NotifPipeline pipeline) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- mDumpManager.registerDumpable(this);
- pipeline.addOnBeforeRenderListListener(mNotifTracker);
- }
+ mDumpManager.registerDumpable(this);
+ pipeline.addOnBeforeRenderListListener(mNotifTracker);
}
@Override
@@ -105,8 +98,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
@Override
public void setGroupExpanded(NotificationEntry entry, boolean expanded) {
NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)
- && entry.getParent() == null) {
+ if (entry.getParent() == null) {
if (expanded) {
throw new IllegalArgumentException("Cannot expand group that is not attached");
} else {
@@ -124,7 +116,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpabl
}
// Only notify listeners if something changed.
- if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE) || changed) {
+ if (changed) {
sendOnGroupExpandedChange(entry, expanded);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
index cb7935369564..da1247953c4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupMembershipManagerImpl.java
@@ -22,8 +22,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlagsClassic;
-import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -38,25 +36,17 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class GroupMembershipManagerImpl implements GroupMembershipManager {
- FeatureFlagsClassic mFeatureFlags;
-
@Inject
- public GroupMembershipManagerImpl(FeatureFlagsClassic featureFlags) {
- mFeatureFlags = featureFlags;
- }
+ public GroupMembershipManagerImpl() {}
@Override
public boolean isGroupSummary(@NonNull NotificationEntry entry) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- if (entry.getParent() == null) {
- // The entry is not attached, so it doesn't count.
- return false;
- }
- // If entry is a summary, its parent is a GroupEntry with summary = entry.
- return entry.getParent().getSummary() == entry;
- } else {
- return getGroupSummary(entry) == entry;
+ if (entry.getParent() == null) {
+ // The entry is not attached, so it doesn't count.
+ return false;
}
+ // If entry is a summary, its parent is a GroupEntry with summary = entry.
+ return entry.getParent().getSummary() == entry;
}
@Nullable
@@ -70,12 +60,8 @@ public class GroupMembershipManagerImpl implements GroupMembershipManager {
@Override
public boolean isChildInGroup(@NonNull NotificationEntry entry) {
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_EXPANSION_CHANGE)) {
- // An entry is a child if it's not a summary or top level entry, but it is attached.
- return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
- } else {
- return !isTopLevelEntry(entry);
- }
+ // An entry is a child if it's not a summary or top level entry, but it is attached.
+ return !isGroupSummary(entry) && !isTopLevelEntry(entry) && entry.getParent() != null;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index 61e6f65b2bc2..8021d8f58ccc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -127,6 +127,9 @@ class ShadeViewDiffer(
}
}
+ /**
+ * Attach the Child Nodes to the parentNode using the structure from specMap
+ */
private fun attachChildren(
parentNode: ShadeNode,
specMap: Map<NodeController, NodeSpec>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index aca8b64c05d2..342828c4b5d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -6,6 +6,7 @@ import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
import android.os.HandlerExecutor
+import android.os.HandlerThread
import android.os.UserHandle
import android.provider.Settings
import com.android.keyguard.KeyguardUpdateMonitor
@@ -87,6 +88,7 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS)
private val onStateChangedListeners = ListenerSet<Consumer<String>>()
private var hideSilentNotificationsOnLockscreen: Boolean = false
+ private val handlerThread: HandlerThread = HandlerThread("KeyguardNotificationVis")
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
@@ -154,7 +156,9 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
notifyStateChanged("onStatusBarUpcomingStateChanged")
}
})
- userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler))
+ handlerThread.start()
+ userTracker.addCallback(userTrackerCallback,
+ HandlerExecutor(handlerThread.getThreadHandler()))
}
override fun addOnStateChangedListener(listener: Consumer<String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 4349b3b5aeb3..c6832bc20e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -247,6 +247,9 @@ public class NotificationLogger implements StateListener, CoreStartable,
public void setUpWithContainer(NotificationListContainer listContainer) {
mListContainer = listContainer;
+ if (mLogging) {
+ mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ }
}
@Override
@@ -294,7 +297,9 @@ public class NotificationLogger implements StateListener, CoreStartable,
lockscreen = mLockscreen != null && mLockscreen;
}
mNotificationPanelLogger.logPanelShown(lockscreen, getVisibleNotifications());
- mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ if (mListContainer != null) {
+ mListContainer.setChildLocationsChangedListener(this::onChildLocationsChanged);
+ }
// Sometimes, the transition from lockscreenOrShadeVisible=false ->
// lockscreenOrShadeVisible=true doesn't cause the scroller to emit child location
// events. Hence generate one ourselves to guarantee that we're reporting visible
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
index d626c18e46f5..8ae324fa4ef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -44,6 +44,7 @@ public abstract class BindStage<Params> extends BindRequester {
/**
* Execute the stage asynchronously.
*
+ * @param entry the NotificationEntry to bind
* @param row notification top-level view to bind views to
* @param callback callback after stage finishes
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index 43d99a0e03f2..6bc2b2f9e250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -16,19 +16,27 @@
package com.android.systemui.statusbar.notification.row;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon;
/**
* A hybrid view which may contain information about one ore more conversations.
@@ -37,6 +45,7 @@ public class HybridConversationNotificationView extends HybridNotificationView {
private ImageView mConversationIconView;
private TextView mConversationSenderName;
+ private ViewStub mConversationFacePileStub;
private View mConversationFacePile;
private int mSingleAvatarSize;
private int mFacePileSize;
@@ -65,7 +74,16 @@ public class HybridConversationNotificationView extends HybridNotificationView {
protected void onFinishInflate() {
super.onFinishInflate();
mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
- mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ mConversationFacePileStub =
+ requireViewById(com.android.internal.R.id.conversation_face_pile);
+ } else {
+ // TODO(b/217799515): This usage is vague because mConversationFacePile represents both
+ // View and ViewStub at different stages of View inflation, should be removed when
+ // AsyncHybridViewInflation flag is removed
+ mConversationFacePile =
+ requireViewById(com.android.internal.R.id.conversation_face_pile);
+ }
mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
applyTextColor(mConversationSenderName, mSecondaryTextColor);
mFacePileSize = getResources()
@@ -85,7 +103,8 @@ public class HybridConversationNotificationView extends HybridNotificationView {
@Override
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
- @Nullable View contentView) {
+ @Nullable View contentView) {
+ AsyncHybridViewInflation.assertInLegacyMode();
if (!(contentView instanceof ConversationLayout)) {
super.bind(title, text, contentView);
return;
@@ -137,6 +156,77 @@ public class HybridConversationNotificationView extends HybridNotificationView {
super.bind(conversationTitle, conversationText, conversationLayout);
}
+ /**
+ * Set the avatar using ConversationAvatar from SingleLineViewModel
+ *
+ * @param conversationAvatar the icon needed for a single-line conversation view, it should be
+ * either an instance of SingleIcon or FacePile
+ */
+ public void setAvatar(@NonNull ConversationAvatar conversationAvatar) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+ if (conversationAvatar instanceof SingleIcon) {
+ SingleIcon avatar = (SingleIcon) conversationAvatar;
+ if (mConversationFacePile != null) mConversationFacePile.setVisibility(GONE);
+ mConversationIconView.setVisibility(VISIBLE);
+ mConversationIconView.setImageDrawable(avatar.getIconDrawable());
+ setSize(mConversationIconView, mSingleAvatarSize);
+ return;
+ }
+
+ // If conversationAvatar is not a SingleIcon, it should be a FacePile.
+ // Bind the face pile with it.
+ FacePile facePileModel = (FacePile) conversationAvatar;
+ mConversationIconView.setVisibility(GONE);
+ // Inflate mConversationFacePile from ViewStub
+ if (mConversationFacePile == null) {
+ mConversationFacePile = mConversationFacePileStub.inflate();
+ }
+ mConversationFacePile.setVisibility(VISIBLE);
+
+ ImageView facePileBottomBg = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom_background);
+ ImageView facePileBottom = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom);
+ ImageView facePileTop = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_top);
+
+ int bottomBackgroundColor = facePileModel.getBottomBackgroundColor();
+ facePileBottomBg.setImageTintList(ColorStateList.valueOf(bottomBackgroundColor));
+
+ facePileBottom.setImageDrawable(facePileModel.getBottomIconDrawable());
+ facePileTop.setImageDrawable(facePileModel.getTopIconDrawable());
+
+ setSize(mConversationFacePile, mFacePileSize);
+ setSize(facePileBottom, mFacePileAvatarSize);
+ setSize(facePileTop, mFacePileAvatarSize);
+ setSize(facePileBottomBg, mFacePileAvatarSize + 2 * mFacePileProtectionWidth);
+
+ mTransformationHelper.addViewTransformingToSimilar(facePileTop);
+ mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
+ mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
+
+ }
+
+ /**
+ * bind the text views
+ */
+ public void setText(
+ CharSequence titleText,
+ CharSequence contentText,
+ CharSequence conversationSenderName
+ ) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+ if (conversationSenderName == null) {
+ mConversationSenderName.setVisibility(GONE);
+ } else {
+ mConversationSenderName.setVisibility(VISIBLE);
+ mConversationSenderName.setText(conversationSenderName);
+ }
+ // TODO (b/217799515): super.bind() doesn't use contentView, remove the contentView
+ // argument when the flag is removed
+ super.bind(/* title = */ titleText, /* text = */ contentText, /* contentView = */ null);
+ }
+
private static void setSize(View view, int size) {
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
lp.width = size;
@@ -153,4 +243,9 @@ public class HybridConversationNotificationView extends HybridNotificationView {
super.setNotificationFaded(faded);
NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded);
}
+
+ @VisibleForTesting
+ TextView getConversationSenderNameView() {
+ return mConversationSenderName;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index ddd9bddc7375..09c034978977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -32,6 +32,7 @@ import android.widget.TextView;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
/**
* A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -41,6 +42,8 @@ public class HybridGroupManager {
private final Context mContext;
+ private static final String TAG = "HybridGroupManager";
+
private float mOverflowNumberSize;
private int mOverflowNumberPadding;
@@ -93,21 +96,34 @@ public class HybridGroupManager {
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
View contentView, StatusBarNotification notification,
ViewGroup parent) {
+ AsyncHybridViewInflation.assertInLegacyMode();
boolean isNewView = false;
if (reusableView == null) {
Trace.beginSection("HybridGroupManager#bindFromNotification");
reusableView = inflateHybridView(contentView, parent);
isNewView = true;
}
- CharSequence titleText = resolveTitle(notification.getNotification());
- CharSequence contentText = resolveText(notification.getNotification());
- reusableView.bind(titleText, contentText, contentView);
+
+ updateReusableView(reusableView, notification, contentView);
if (isNewView) {
Trace.endSection();
}
return reusableView;
}
+ /**
+ * Update the HybridNotificationView (single-line view)'s appearance
+ */
+ public void updateReusableView(HybridNotificationView reusableView,
+ StatusBarNotification notification, View contentView) {
+ AsyncHybridViewInflation.assertInLegacyMode();
+ final CharSequence titleText = resolveTitle(notification.getNotification());
+ final CharSequence contentText = resolveText(notification.getNotification());
+ if (reusableView != null) {
+ reusableView.bind(titleText, contentText, contentView);
+ }
+ }
+
@Nullable
public static CharSequence resolveText(Notification notification) {
CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
index d10b556de0fb..8bc8e8cc49a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactory.kt
@@ -22,11 +22,9 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
-import com.android.systemui.statusbar.notification.row.NotificationRowModule.NOTIF_REMOTEVIEWS_FACTORIES
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
-import javax.inject.Named
/**
* Implementation of [NotifLayoutInflaterFactory]. This class uses a set of
@@ -37,8 +35,7 @@ class NotifLayoutInflaterFactory
constructor(
@Assisted private val row: ExpandableNotificationRow,
@Assisted @InflationFlag val layoutType: Int,
- @Named(NOTIF_REMOTEVIEWS_FACTORIES)
- private val remoteViewsFactories: Set<@JvmSuppressWildcards NotifRemoteViewsFactory>
+ private val notifRemoteViewsFactoryContainer: NotifRemoteViewsFactoryContainer
) : LayoutInflater.Factory2 {
override fun onCreateView(
@@ -49,7 +46,7 @@ constructor(
): View? {
var handledFactory: NotifRemoteViewsFactory? = null
var result: View? = null
- for (layoutFactory in remoteViewsFactories) {
+ for (layoutFactory in notifRemoteViewsFactoryContainer.factories) {
layoutFactory.instantiate(row, layoutType, parent, name, context, attrs)?.run {
check(handledFactory == null) {
"$layoutFactory tries to produce name:$name with type:$layoutType. " +
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
new file mode 100644
index 000000000000..99177c270b32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import javax.inject.Inject
+
+interface NotifRemoteViewsFactoryContainer {
+ val factories: Set<NotifRemoteViewsFactory>
+}
+
+class NotifRemoteViewsFactoryContainerImpl
+@Inject
+constructor(
+ featureFlags: FeatureFlags,
+ precomputedTextViewFactory: PrecomputedTextViewFactory,
+ bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
+ callLayoutSetDataAsyncFactory: CallLayoutSetDataAsyncFactory,
+) : NotifRemoteViewsFactoryContainer {
+ override val factories: Set<NotifRemoteViewsFactory> = buildSet {
+ add(precomputedTextViewFactory)
+ if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
+ add(bigPictureLayoutInflaterFactory)
+ }
+ if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
+ add(callLayoutSetDataAsyncFactory)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index f186e665f773..913d5f6d3848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -20,6 +20,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
+import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_SINGLELINE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -41,15 +42,19 @@ import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -135,7 +140,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
AsyncInflationTask task = new AsyncInflationTask(
mBgExecutor,
mInflateSynchronously,
- contentToBind,
+ /* reInflateFlags = */ contentToBind,
mRemoteViewCache,
entry,
mConversationProcessor,
@@ -145,7 +150,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
bindParams.usesIncreasedHeadsUpHeight,
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
- mIsMediaInQS,
+ /* isMediaFlagEnabled = */ mIsMediaInQS,
mSmartReplyStateInflater,
mNotifLayoutInflaterFactoryProvider,
mLogger);
@@ -178,6 +183,29 @@ public class NotificationContentInflater implements NotificationRowContentBinder
result = inflateSmartReplyViews(result, reInflateFlags, entry, row.getContext(),
packageContext, row.getExistingSmartReplyState(), smartRepliesInflater, mLogger);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ boolean isConversation = entry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation) {
+ messagingStyle = mConversationProcessor
+ .processNotification(entry, builder, mLogger);
+ }
+ result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ .inflateSingleLineViewModel(
+ entry.getSbn().getNotification(),
+ messagingStyle,
+ builder,
+ row.getContext()
+ );
+ result.mInflatedSingleLineViewHolder =
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ isConversation,
+ reInflateFlags,
+ entry,
+ row.getContext(),
+ mLogger
+ );
+ }
apply(
mBgExecutor,
@@ -255,6 +283,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
});
break;
+ case FLAG_CONTENT_VIEW_SINGLE_LINE: {
+ if (AsyncHybridViewInflation.isEnabled()) {
+ row.getPrivateLayout().performWhenContentInactive(
+ VISIBLE_TYPE_SINGLELINE,
+ () -> row.getPrivateLayout().setSingleLineView(null)
+ );
+ }
+ break;
+ }
default:
break;
}
@@ -282,6 +319,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder
if ((contentViews & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
row.getPublicLayout().removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED);
}
+ if (AsyncHybridViewInflation.isEnabled()
+ && (contentViews & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+ row.getPrivateLayout().removeContentInactiveRunnable(VISIBLE_TYPE_SINGLELINE);
+ }
}
private static InflationProgress inflateSmartReplyViews(
@@ -772,6 +813,25 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
setRepliesAndActions = true;
}
+
+ if (AsyncHybridViewInflation.isEnabled()
+ && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+ HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+ SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+ if (viewHolder != null && viewModel != null) {
+ if (viewModel.isConversation()) {
+ SingleLineConversationViewBinder.bind(
+ result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder
+ );
+ } else {
+ SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder);
+ }
+ privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+ }
+ }
+
if (setRepliesAndActions) {
privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
}
@@ -941,19 +1001,23 @@ public class NotificationContentInflater implements NotificationRowContentBinder
// For all of our templates, we want it to be RTL
packageContext = new RtlEnabledContext(packageContext);
}
- if (mEntry.getRanking().isConversation()) {
- mConversationProcessor.processNotification(mEntry, recoveredBuilder, mLogger);
+ boolean isConversation = mEntry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation) {
+ messagingStyle = mConversationProcessor.processNotification(
+ mEntry, recoveredBuilder, mLogger);
}
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext, mRow,
mNotifLayoutInflaterFactoryProvider, mLogger);
+
mLogger.logAsyncTaskProgress(mEntry,
"getting existing smart reply state (on wrong thread!)");
InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
InflationProgress result = inflateSmartReplyViews(
- inflationProgress,
+ /* result = */ inflationProgress,
mReInflateFlags,
mEntry,
mContext,
@@ -962,6 +1026,27 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mSmartRepliesInflater,
mLogger);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ // Inflate the single-line content view's ViewModel and ViewHolder from the
+ // background thread, the ViewHolder needs to be bind with ViewModel later from
+ // the main thread.
+ result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ .inflateSingleLineViewModel(
+ mEntry.getSbn().getNotification(),
+ messagingStyle,
+ recoveredBuilder,
+ mContext
+ );
+ result.mInflatedSingleLineViewHolder =
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ isConversation,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ mLogger
+ );
+ }
+
mLogger.logAsyncTaskProgress(mEntry,
"getting row image resolver (on wrong thread!)");
final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
@@ -1078,6 +1163,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private InflatedSmartReplyState inflatedSmartReplyState;
private InflatedSmartReplyViewHolder expandedInflatedSmartReplies;
private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies;
+
+ // ViewModel for SingleLineView, holds the UI State
+ SingleLineViewModel mInflatedSingleLineViewModel;
+ // Inflated SingleLineViewHolder, SingleLineView that lacks the UI State
+ HybridNotificationView mInflatedSingleLineViewHolder;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
index 4f5455dc455f..ee9462c60674 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
@@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import javax.inject.Inject
@@ -99,6 +100,26 @@ constructor(@NotifInflationLog private val buffer: LogBuffer) {
)
}
+ fun logInflateSingleLine(
+ entry: NotificationEntry,
+ @InflationFlag inflationFlags: Int,
+ isConversation: Boolean
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ int1 = inflationFlags
+ bool1 = isConversation
+ },
+ {
+ "inflateSingleLineView, inflationFlags: ${flagToString(int1)} for $str1, " +
+ "isConversation: $bool1"
+ }
+ )
+ }
+
companion object {
fun flagToString(@InflationFlag flag: Int): String {
if (flag == 0) {
@@ -121,6 +142,9 @@ constructor(@NotifInflationLog private val buffer: LogBuffer) {
if (flag and FLAG_CONTENT_VIEW_PUBLIC != 0) {
l.add("PUBLIC")
}
+ if (flag and FLAG_CONTENT_VIEW_SINGLE_LINE != 0) {
+ l.add("SINGLE_LINE")
+ }
return l.joinToString("|")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index a1718b9fbb02..402ea51bebb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
@@ -86,7 +87,7 @@ public class NotificationContentView extends FrameLayout implements Notification
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
- private static final int VISIBLE_TYPE_SINGLELINE = 3;
+ public static final int VISIBLE_TYPE_SINGLELINE = 3;
/**
* Used when there is no content on the view such as when we're a public layout but don't
* need to show.
@@ -98,6 +99,7 @@ public class NotificationContentView extends FrameLayout implements Notification
private final Rect mClipBounds = new Rect();
private int mMinContractedHeight;
+ private int mMinSingleLineHeight;
private View mContractedChild;
private View mExpandedChild;
private View mHeadsUpChild;
@@ -234,6 +236,11 @@ public class NotificationContentView extends FrameLayout implements Notification
public void reinflate() {
mMinContractedHeight = getResources().getDimensionPixelSize(
R.dimen.min_notification_layout_height);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ //TODO: set the height with a more reasonable min single-line height
+ mMinSingleLineHeight = getResources().getDimensionPixelSize(
+ R.dimen.conversation_single_line_face_pile_size);
+ }
}
public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight) {
@@ -540,6 +547,28 @@ public class NotificationContentView extends FrameLayout implements Notification
updateShownWrapper(mVisibleType);
}
+ /**
+ * Sets the single-line view. Child may be null to remove the view.
+ * @param child single-line content view to set
+ */
+ public void setSingleLineView(@Nullable HybridNotificationView child) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
+ if (mSingleLineView != null) {
+ mOnContentViewInactiveListeners.remove(mSingleLineView);
+ mSingleLineView.animate().cancel();
+ removeView(mSingleLineView);
+ }
+ if (child == null) {
+ mSingleLineView = null;
+ if (mTransformationStartVisibleType == VISIBLE_TYPE_SINGLELINE) {
+ mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
+ }
+ return;
+ }
+ addView(child);
+ mSingleLineView = child;
+ }
+
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
@@ -809,7 +838,17 @@ public class NotificationContentView extends FrameLayout implements Notification
return mContractedChild != null
? getViewHeight(VISIBLE_TYPE_CONTRACTED) : mMinContractedHeight;
} else {
- return mSingleLineView.getHeight();
+ if (AsyncHybridViewInflation.isEnabled()) {
+ if (mSingleLineView != null) {
+ return getViewHeight(VISIBLE_TYPE_SINGLELINE);
+ } else {
+ Log.wtf(TAG, "getMinHeight: mSingleLineView == null");
+ return mMinSingleLineHeight;
+ }
+ } else {
+ AsyncHybridViewInflation.assertInLegacyMode();
+ return mSingleLineView.getHeight();
+ }
}
}
@@ -1264,19 +1303,30 @@ public class NotificationContentView extends FrameLayout implements Notification
}
private void updateSingleLineView() {
- if (mIsChildInGroup) {
+ try {
Trace.beginSection("NotifContentView#updateSingleLineView");
- boolean isNewView = mSingleLineView == null;
- mSingleLineView = mHybridGroupManager.bindFromNotification(
- mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this);
- if (isNewView) {
- updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
- mSingleLineView, mSingleLineView);
+ if (AsyncHybridViewInflation.isEnabled()) {
+ return;
+ }
+ AsyncHybridViewInflation.assertInLegacyMode();
+ if (mIsChildInGroup) {
+ boolean isNewView = mSingleLineView == null;
+ mSingleLineView = mHybridGroupManager.bindFromNotification(
+ /* reusableView = */ mSingleLineView,
+ /* contentView = */ mContractedChild,
+ /* notification = */ mNotificationEntry.getSbn(),
+ /* parent = */ this
+ );
+ if (isNewView && mSingleLineView != null) {
+ updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
+ mSingleLineView, mSingleLineView);
+ }
+ } else if (mSingleLineView != null) {
+ removeView(mSingleLineView);
+ mSingleLineView = null;
}
+ } finally {
Trace.endSection();
- } else if (mSingleLineView != null) {
- removeView(mSingleLineView);
- mSingleLineView = null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index d7b7aa210257..736140c44dfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -80,6 +80,7 @@ public interface NotificationRowContentBinder {
FLAG_CONTENT_VIEW_EXPANDED,
FLAG_CONTENT_VIEW_HEADS_UP,
FLAG_CONTENT_VIEW_PUBLIC,
+ FLAG_CONTENT_VIEW_SINGLE_LINE,
FLAG_CONTENT_VIEW_ALL})
@interface InflationFlag {}
/**
@@ -102,7 +103,12 @@ public interface NotificationRowContentBinder {
*/
int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
- int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1;
+ /**
+ * The single line notification view. Show when the notification is shown as a child in group.
+ */
+ int FLAG_CONTENT_VIEW_SINGLE_LINE = 1 << 4;
+
+ int FLAG_CONTENT_VIEW_ALL = (1 << 5) - 1;
/**
* Parameters for content view binding
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 46ddba4d4d8d..200a08a2ea65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,26 +17,15 @@
package com.android.systemui.statusbar.notification.row;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import dagger.Binds;
import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.ElementsIntoSet;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.inject.Named;
/**
* Dagger Module containing notification row and view inflation implementations.
*/
@Module
public abstract class NotificationRowModule {
- public static final String NOTIF_REMOTEVIEWS_FACTORIES =
- "notif_remoteviews_factories";
/**
* Provides notification row content binder instance.
@@ -54,24 +43,11 @@ public abstract class NotificationRowModule {
public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
NotifRemoteViewCacheImpl cacheImpl);
- /** Provides view factories to be inflated in notification content. */
- @Provides
- @ElementsIntoSet
- @Named(NOTIF_REMOTEVIEWS_FACTORIES)
- static Set<NotifRemoteViewsFactory> provideNotifRemoteViewsFactories(
- FeatureFlags featureFlags,
- PrecomputedTextViewFactory precomputedTextViewFactory,
- BigPictureLayoutInflaterFactory bigPictureLayoutInflaterFactory,
- CallLayoutSetDataAsyncFactory callLayoutSetDataAsyncFactory
- ) {
- final Set<NotifRemoteViewsFactory> replacementFactories = new HashSet<>();
- replacementFactories.add(precomputedTextViewFactory);
- if (featureFlags.isEnabled(Flags.BIGPICTURE_NOTIFICATION_LAZY_LOADING)) {
- replacementFactories.add(bigPictureLayoutInflaterFactory);
- }
- if (featureFlags.isEnabled(Flags.CALL_LAYOUT_ASYNC_SET_DATA)) {
- replacementFactories.add(callLayoutSetDataAsyncFactory);
- }
- return replacementFactories;
- }
+ /**
+ * Provides notification remote view factory container
+ */
+ @Binds
+ @SysUISingleton
+ public abstract NotifRemoteViewsFactoryContainer provideNotifRemoteViewsFactoryContainer(
+ NotifRemoteViewsFactoryContainerImpl containerImpl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index a52f638e7c26..1494c275d061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -102,9 +102,9 @@ public final class RowContentBindParams {
* @see InflationFlag
*/
public void markContentViewsFreeable(@InflationFlag int contentViews) {
- @InflationFlag int existingContentViews = contentViews &= mContentViews;
+ @InflationFlag int existingFreeableContentViews = contentViews &= mContentViews;
mContentViews &= ~contentViews;
- mDirtyContentViews |= existingContentViews;
+ mDirtyContentViews |= existingFreeableContentViews;
}
public @InflationFlag int getContentViews() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index b70da00ad517..f4f8374d0a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -63,7 +63,10 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
@InflationFlag int inflationFlags = params.getContentViews();
@InflationFlag int invalidatedFlags = params.getDirtyContentViews();
+ // Rebind the content views which are needed now, and the corresponding old views are
+ // invalidated
@InflationFlag int contentToBind = invalidatedFlags & inflationFlags;
+ // Unbind the content views that are not needed
@InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL;
// Bind/unbind with parameters
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
new file mode 100644
index 000000000000..d6118a0b3865
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -0,0 +1,390 @@
+/*
+ * 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.row
+
+import android.app.Notification
+import android.app.Notification.MessagingStyle
+import android.app.Person
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.util.Log
+import android.view.LayoutInflater
+import com.android.app.tracing.traceSection
+import com.android.internal.R
+import com.android.internal.widget.MessagingMessage
+import com.android.internal.widget.PeopleHelper
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationData
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+/** The inflater of SingleLineViewModel and SingleLineViewHolder */
+internal object SingleLineViewInflater {
+ const val TAG = "SingleLineViewInflater"
+
+ /**
+ * Inflate an instance of SingleLineViewModel.
+ *
+ * @param notification the notification to show
+ * @param messagingStyle the MessagingStyle information is only provided for conversation
+ * notification, not for legacy messaging notifications
+ * @param builder the recovered Notification Builder
+ * @param systemUiContext the context of Android System UI
+ * @return the inflated SingleLineViewModel
+ */
+ @JvmStatic
+ fun inflateSingleLineViewModel(
+ notification: Notification,
+ messagingStyle: MessagingStyle?,
+ builder: Notification.Builder,
+ systemUiContext: Context,
+ ): SingleLineViewModel {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return SingleLineViewModel(null, null, null)
+ }
+ peopleHelper.init(systemUiContext)
+ var titleText = HybridGroupManager.resolveTitle(notification)
+ var contentText = HybridGroupManager.resolveText(notification)
+
+ if (messagingStyle == null) {
+ return SingleLineViewModel(
+ titleText = titleText,
+ contentText = contentText,
+ conversationData = null,
+ )
+ }
+
+ val isGroupConversation = messagingStyle.isGroupConversation
+
+ val conversationTextData = messagingStyle.loadConversationTextData(systemUiContext)
+ if (conversationTextData?.conversationTitle?.isNotEmpty() == true) {
+ titleText = conversationTextData.conversationTitle
+ }
+ if (conversationTextData?.conversationText?.isNotEmpty() == true) {
+ contentText = conversationTextData.conversationText
+ }
+
+ val conversationAvatar =
+ messagingStyle.loadConversationAvatar(
+ notification = notification,
+ isGroupConversation = isGroupConversation,
+ builder = builder,
+ systemUiContext = systemUiContext
+ )
+
+ val conversationData =
+ ConversationData(
+ // We don't show the sender's name for one-to-one conversation
+ conversationSenderName =
+ if (isGroupConversation) conversationTextData?.senderName else null,
+ avatar = conversationAvatar
+ )
+
+ return SingleLineViewModel(
+ titleText = titleText,
+ contentText = contentText,
+ conversationData = conversationData,
+ )
+ }
+
+ /** load conversation text data from the MessagingStyle of conversation notifications */
+ private fun MessagingStyle.loadConversationTextData(
+ systemUiContext: Context
+ ): ConversationTextData? {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return null
+ }
+ var conversationText: CharSequence?
+
+ if (messages.isEmpty()) {
+ return null
+ }
+
+ // load the conversation text
+ val lastMessage = messages[messages.lastIndex]
+ conversationText = lastMessage.text
+ if (conversationText == null && lastMessage.isImageMessage()) {
+ conversationText = findBackUpConversationText(lastMessage, systemUiContext)
+ }
+
+ // load the sender's name to display
+ val name = lastMessage.senderPerson?.name
+ val senderName =
+ systemUiContext.resources.getString(
+ R.string.conversation_single_line_name_display,
+ name
+ )
+
+ // We need to find back-up values for those texts if they are needed and empty
+ return ConversationTextData(
+ conversationTitle = conversationTitle
+ ?: findBackUpConversationTitle(senderName, systemUiContext),
+ conversationText = conversationText,
+ senderName = senderName,
+ )
+ }
+
+ private fun MessagingStyle.Message.isImageMessage(): Boolean = MessagingMessage.hasImage(this)
+
+ /** find a back-up conversation title when the conversation title is null. */
+ private fun MessagingStyle.findBackUpConversationTitle(
+ senderName: CharSequence?,
+ systemUiContext: Context,
+ ): CharSequence {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return ""
+ }
+ return if (isGroupConversation) {
+ systemUiContext.resources.getString(R.string.conversation_title_fallback_group_chat)
+ } else {
+ // Is one-to-one, let's try to use the last sender's name
+ // The last back-up is the value of resource: conversation_title_fallback_one_to_one
+ senderName
+ ?: systemUiContext.resources.getString(
+ R.string.conversation_title_fallback_one_to_one
+ )
+ }
+ }
+
+ /**
+ * find a back-up conversation text when the conversation has null text and is image message.
+ */
+ private fun findBackUpConversationText(
+ message: MessagingStyle.Message,
+ context: Context,
+ ): CharSequence? {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return null
+ }
+ // If the message is not an image message, just return empty, the back-up text for showing
+ // will be SingleLineViewModel.contentText
+ if (!message.isImageMessage()) return null
+ // If is image message, return a placeholder
+ return context.resources.getString(R.string.conversation_single_line_image_placeholder)
+ }
+
+ /**
+ * The text data that we load from a conversation notification to show in the single-line views.
+ *
+ * Group conversation single-line view should be formatted as:
+ * [conversationTitle, senderName, conversationText]
+ *
+ * One-to-one single-line view should be formatted as:
+ * [conversationTitle (which is equal to the senderName), conversationText]
+ *
+ * @property conversationTitle the title of the conversation, not necessarily the title of the
+ * notification row. conversationTitle is non-null, though may be empty, in which case we need
+ * to show the notification title instead.
+ * @property conversationText the text content of the conversation, single-line will use the
+ * notification's text when conversationText is null
+ * @property senderName the sender's name to be shown in the row when needed. senderName can be
+ * null
+ */
+ data class ConversationTextData(
+ val conversationTitle: CharSequence,
+ val conversationText: CharSequence?,
+ val senderName: CharSequence?,
+ )
+
+ private fun groupMessages(
+ messages: List<MessagingStyle.Message>,
+ historicMessages: List<MessagingStyle.Message>,
+ ): List<MutableList<MessagingStyle.Message>> {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return listOf()
+ }
+ if (messages.isEmpty() && historicMessages.isEmpty()) return listOf()
+ var currentGroup: MutableList<MessagingStyle.Message>? = null
+ var currentSenderKey: CharSequence? = null
+ val groups = mutableListOf<MutableList<MessagingStyle.Message>>()
+ for (i in 0 until (historicMessages.size + messages.size)) {
+ val message = if (i < historicMessages.size) historicMessages[i] else messages[i]
+
+ val sender = message.senderPerson
+ val senderKey = sender?.getKeyOrName()
+ val isNewGroup = (currentGroup == null) || senderKey != currentSenderKey
+ if (isNewGroup) {
+ currentGroup = mutableListOf()
+ groups.add(currentGroup)
+ currentSenderKey = senderKey
+ }
+ currentGroup?.add(message)
+ }
+ return groups
+ }
+
+ private fun MessagingStyle.loadConversationAvatar(
+ builder: Notification.Builder,
+ notification: Notification,
+ isGroupConversation: Boolean,
+ systemUiContext: Context,
+ ): ConversationAvatar {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) {
+ return SingleIcon(null)
+ }
+ val userKey = user.getKeyOrName()
+ var conversationIcon: Icon? = null
+ var conversationText: CharSequence? = conversationTitle
+
+ val groups = groupMessages(messages, historicMessages)
+ val uniqueNames = peopleHelper.mapUniqueNamesToPrefixWithGroupList(groups)
+
+ if (!isGroupConversation) {
+ // Conversation is one-to-one, load the single icon
+ // Let's resolve the icon / text from the last sender
+ if (shortcutIcon != null) {
+ conversationIcon = shortcutIcon
+ }
+
+ for (i in messages.lastIndex downTo 0) {
+ val message = messages[i]
+ val sender = message.senderPerson
+ val senderKey = sender?.getKeyOrName()
+ if ((sender != null && senderKey != userKey) || i == 0) {
+ if (conversationText.isNullOrEmpty()) {
+ // We use the senderName as header text if no conversation title is provided
+ // (This usually happens for most 1:1 conversations)
+ conversationText = sender?.name ?: ""
+ }
+ if (conversationIcon == null) {
+ var avatarIcon = sender?.icon
+ if (avatarIcon == null) {
+ avatarIcon = builder.getDefaultAvatar(name = conversationText)
+ }
+ conversationIcon = avatarIcon
+ }
+ break
+ }
+ }
+ }
+
+ if (conversationIcon == null) {
+ conversationIcon = notification.getLargeIcon()
+ }
+
+ // If is one-to-one or the conversation has an icon, return a single icon
+ if (!isGroupConversation || conversationIcon != null) {
+ return SingleIcon(conversationIcon?.loadDrawable(systemUiContext))
+ }
+
+ // Otherwise, let's find the two last conversations to build a face pile:
+ var secondLastIcon: Icon? = null
+ var lastIcon: Icon? = null
+ var lastKey: CharSequence? = null
+
+ for (i in groups.lastIndex downTo 0) {
+ val message = groups[i][0]
+ val sender = message.senderPerson ?: user
+ val senderKey = sender.getKeyOrName()
+ val notUser = senderKey != userKey
+ val notIncluded = senderKey != lastKey
+
+ if ((notUser && notIncluded) || (i == 0 && lastKey == null)) {
+ if (lastIcon == null) {
+ lastIcon =
+ sender.icon
+ ?: builder.getDefaultAvatar(
+ name = sender.name,
+ uniqueNames = uniqueNames
+ )
+ lastKey = senderKey
+ } else {
+ secondLastIcon =
+ sender.icon
+ ?: builder.getDefaultAvatar(
+ name = sender.name,
+ uniqueNames = uniqueNames
+ )
+ break
+ }
+ }
+ }
+
+ if (lastIcon == null) {
+ lastIcon = builder.getDefaultAvatar(name = "")
+ }
+
+ if (secondLastIcon == null) {
+ secondLastIcon = builder.getDefaultAvatar(name = "")
+ }
+
+ return FacePile(
+ topIconDrawable = secondLastIcon.loadDrawable(systemUiContext),
+ bottomIconDrawable = lastIcon.loadDrawable(systemUiContext),
+ bottomBackgroundColor = builder.getBackgroundColor(/* isHeader = */ false),
+ )
+ }
+
+ @JvmStatic
+ fun inflateSingleLineViewHolder(
+ isConversation: Boolean,
+ reinflateFlags: Int,
+ entry: NotificationEntry,
+ context: Context,
+ logger: NotificationContentInflaterLogger,
+ ): HybridNotificationView? {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null
+ if (reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE == 0) {
+ return null
+ }
+
+ logger.logInflateSingleLine(entry, reinflateFlags, isConversation)
+ logger.logAsyncTaskProgress(entry, "inflating single-line content view")
+
+ var view: HybridNotificationView? = null
+
+ traceSection("NotificationContentInflater#inflateSingleLineView") {
+ val inflater = LayoutInflater.from(context)
+ val layoutRes: Int =
+ if (isConversation)
+ com.android.systemui.res.R.layout.hybrid_conversation_notification
+ else com.android.systemui.res.R.layout.hybrid_notification
+ view = inflater.inflate(layoutRes, /* root = */ null) as HybridNotificationView
+ if (view == null) {
+ Log.wtf(TAG, "Single-line view inflation result is null for entry: ${entry.logKey}")
+ }
+ }
+ return view
+ }
+
+ private fun Notification.Builder.getDefaultAvatar(
+ name: CharSequence?,
+ uniqueNames: PeopleHelper.NameToPrefixMap? = null
+ ): Icon {
+ val layoutColor = getSmallIconColor(/* isHeader = */ false)
+ if (!name.isNullOrEmpty()) {
+ val symbol = uniqueNames?.getPrefix(name) ?: ""
+ return peopleHelper.createAvatarSymbol(
+ /* name = */ name,
+ /* symbol = */ symbol,
+ /* layoutColor = */ layoutColor
+ )
+ }
+ // If name is null, create default avatar with background color
+ // TODO(b/319829062): Investigate caching default icon for color
+ return peopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", layoutColor)
+ }
+
+ private fun Person.getKeyOrName(): CharSequence? = if (key == null) name else key
+
+ private val peopleHelper = PeopleHelper()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
new file mode 100644
index 000000000000..69284bd7ef48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineConversationViewBinder.kt
@@ -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.statusbar.notification.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridConversationNotificationView
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineConversationViewBinder {
+ @JvmStatic
+ fun bind(viewModel: SingleLineViewModel, view: HybridNotificationView?) {
+ if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return
+ if (view !is HybridConversationNotificationView || !viewModel.isConversation()) {
+ SingleLineViewBinder.bind(viewModel, view)
+ return
+ }
+
+ viewModel.conversationData?.avatar?.let { view.setAvatar(it) }
+ view.setText(
+ viewModel.titleText,
+ viewModel.contentText,
+ viewModel.conversationData?.conversationSenderName
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
new file mode 100644
index 000000000000..22e10c165521
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/SingleLineViewBinder.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.row.ui.viewbinder
+
+import com.android.systemui.statusbar.notification.row.HybridNotificationView
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+object SingleLineViewBinder {
+ @JvmStatic
+ fun bind(viewModel: SingleLineViewModel?, view: HybridNotificationView?) {
+ // bind the title and content text views
+ view?.apply {
+ bind(
+ /* title = */ viewModel?.titleText,
+ /* text = */ viewModel?.contentText,
+ /* contentView = */ null
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
new file mode 100644
index 000000000000..d583fa5d97ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/SingleLineViewModel.kt
@@ -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.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.graphics.drawable.Drawable
+
+/**
+ * ViewModel for SingleLine Notification View.
+ *
+ * @property titleText the text of notification view title
+ * @property contentText the text of view content
+ * @property conversationData the data that is needed specifically for conversation single-line
+ * views. Null conversationData shows that the notification is not conversation. Legacy
+ * MessagingStyle Notifications doesn't have this member.
+ */
+data class SingleLineViewModel(
+ var titleText: CharSequence?,
+ var contentText: CharSequence?,
+ var conversationData: ConversationData?,
+) {
+ fun isConversation(): Boolean {
+ return conversationData != null
+ }
+}
+
+/**
+ * @property conversationSenderName the name of sender to show in the single-line view. Only group
+ * conversation single-line views show the sender name.
+ * @property avatar the avatar to show for the conversation
+ */
+data class ConversationData(
+ val conversationSenderName: CharSequence?,
+ val avatar: ConversationAvatar,
+)
+
+/**
+ * An avatar to show for a single-line conversation notification, it can be either a single icon or
+ * a face pile.
+ */
+sealed class ConversationAvatar
+
+data class SingleIcon(val iconDrawable: Drawable?) : ConversationAvatar()
+
+/**
+ * A kind of avatar to show for a group conversation notification view. It consists of two avatars
+ * of the last two senders.
+ */
+data class FacePile(
+ val topIconDrawable: Drawable?,
+ val bottomIconDrawable: Drawable?,
+ @ColorInt val bottomBackgroundColor: Int
+) : ConversationAvatar()
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 45b9c269b61c..abf6c27c68ac 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
@@ -1295,8 +1295,8 @@ public class NotificationChildrenContainer extends ViewGroup
if (singleLineView != null) {
minExpandHeight += singleLineView.getHeight();
} else {
- Log.e(TAG, "getMinHeight: child " + child + " single line view is null",
- new Exception());
+ Log.e(TAG, "getMinHeight: child " + child.getEntry().getKey()
+ + " single line view is null", new Exception());
}
visibleChildren++;
}
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 04db653282ac..dd04531b6b34 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
@@ -4917,30 +4917,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void removeContainerView(View v) {
Assert.isMainThread();
removeView(v);
- if (!FooterViewRefactor.isEnabled()) {
- // A notification was removed, and we're not currently showing the empty shade view.
- if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
- }
- }
-
updateSpeedBumpIndex();
}
public void addContainerView(View v) {
Assert.isMainThread();
addView(v);
- if (!FooterViewRefactor.isEnabled()) {
- // A notification was added, and we're currently showing the empty shade view.
- if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
- }
- }
-
updateSpeedBumpIndex();
}
@@ -4948,14 +4930,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
Assert.isMainThread();
ensureRemovedFromTransientContainer(v);
addView(v, index);
- // A notification was added, and we're currently showing the empty shade view.
- if (!FooterViewRefactor.isEnabled() && v instanceof ExpandableNotificationRow
- && mController.isShowingEmptyShadeView()) {
- mController.updateShowEmptyShadeView();
- updateFooter();
- mController.updateImportantForAccessibility();
- }
-
updateSpeedBumpIndex();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 1143481863f5..49fde3984acc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -2137,6 +2137,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
if (!FooterViewRefactor.isEnabled()) {
updateShowEmptyShadeView();
+ updateImportantForAccessibility();
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index f842e304ffdf..fe5bdd41a94f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -18,9 +18,12 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
+import android.view.View
+import android.view.WindowInsets
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -30,6 +33,9 @@ import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificat
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** Binds the shared notification container to its view-model. */
@@ -65,6 +71,8 @@ object SharedNotificationContainerBinder {
}
}
+ val burnInParams = MutableStateFlow(BurnInParameters())
+
/*
* For animation sensitive coroutines, immediately run just like applicationScope does
* instead of doing a post() to the main thread. This extra delay can cause visible jitter.
@@ -122,7 +130,11 @@ object SharedNotificationContainerBinder {
}
}
- launch { viewModel.translationY.collect { controller.setTranslationY(it) } }
+ launch {
+ burnInParams
+ .flatMapLatest { params -> viewModel.translationY(params) }
+ .collect { y -> controller.setTranslationY(y) }
+ }
launch {
viewModel.expansionAlpha.collect { controller.setMaxAlphaForExpansion(it) }
@@ -137,11 +149,20 @@ object SharedNotificationContainerBinder {
controller.setOnHeightChangedRunnable(Runnable { viewModel.notificationStackChanged() })
+ view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
+ val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
+ insets
+ }
+
return object : DisposableHandle {
override fun dispose() {
disposableHandle.dispose()
disposableHandleMainImmediate.dispose()
controller.setOnHeightChangedRunnable(null)
+ view.setOnApplyWindowInsetsListener(null)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 99cd89b84c14..4617ce49f44a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -28,6 +28,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
@@ -65,10 +67,11 @@ constructor(
keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val shadeInteractor: ShadeInteractor,
communalInteractor: CommunalInteractor,
- occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
- lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel
+ lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+ private val aodBurnInViewModel: AodBurnInViewModel,
) {
private val statesForConstrainedNotifications =
setOf(
@@ -313,20 +316,22 @@ constructor(
* Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
* translated as the keyguard fades out.
*/
- val translationY: Flow<Float> =
- combine(
+ fun translationY(params: BurnInParameters): Flow<Float> {
+ return combine(
+ aodBurnInViewModel.translationY(params).onStart { emit(0f) },
isOnLockscreenWithoutShade,
merge(
keyguardInteractor.keyguardTranslationY,
occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
)
- ) { isOnLockscreenWithoutShade, translationY ->
+ ) { burnInY, isOnLockscreenWithoutShade, translationY ->
if (isOnLockscreenWithoutShade) {
- translationY
+ burnInY + translationY
} else {
0f
}
}
+ }
/**
* When on keyguard, there is limited space to display notifications so calculate how many could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 63194c37bbe1..8a56da3dafab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -797,18 +797,7 @@ constructor(
}
}
if (dismissShade) {
- if (
- shadeControllerLazy.get().isExpandedVisible &&
- !statusBarKeyguardViewManagerLazy.get().isBouncerShowing
- ) {
- shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
- } else {
- // Do it after DismissAction has been processed to conserve the
- // needed ordering.
- postOnUiThread {
- shadeControllerLazy.get().runPostCollapseRunnables()
- }
- }
+ shadeControllerLazy.get().collapseShadeForActivityStart()
}
return deferred
}
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 266c19c09941..7952511addfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -2587,8 +2587,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// So if AOD is off or unsupported we need to trigger these updates at screen on
// when the keyguard is occluded.
mLockscreenUserManager.updatePublicMode();
- mShadeSurface.getNotificationStackScrollLayoutController()
- .updateSensitivenessForOccludedWakeup();
+ mStackScrollerController.updateSensitivenessForOccludedWakeup();
}
if (mLaunchCameraWhenFinishedWaking) {
mCameraLauncherLazy.get().launchCamera(mLastCameraLaunchSource,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index e3b65ab27f48..61bd112121bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -20,6 +20,7 @@ import android.graphics.Color;
import android.os.Trace;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.res.R;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -39,8 +40,8 @@ public enum ScrimState {
OFF {
@Override
public void prepare(ScrimState previousState) {
- mFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
+ mBehindTint = mBackgroundColor;
mFrontAlpha = 1f;
mBehindAlpha = 1f;
@@ -74,15 +75,15 @@ public enum ScrimState {
} else {
mAnimationDuration = ScrimController.ANIMATION_DURATION;
}
- mFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
- mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+ mFrontTint = mBackgroundColor;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
mFrontAlpha = 0;
mBehindAlpha = mClipQsScrim ? 1 : mScrimBehindAlphaKeyguard;
mNotifAlpha = mClipQsScrim ? mScrimBehindAlphaKeyguard : 0;
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
},
@@ -93,10 +94,10 @@ public enum ScrimState {
// notif scrim alpha values are determined by ScrimController#applyState
// based on the shade expansion
- mFrontTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
mFrontAlpha = .66f;
- mBehindTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
mBehindAlpha = 1f;
}
},
@@ -110,7 +111,7 @@ public enum ScrimState {
mBehindTint = previousState.mBehindTint;
mBehindAlpha = previousState.mBehindAlpha;
- mFrontTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
mFrontAlpha = .66f;
}
},
@@ -122,7 +123,7 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
- mBehindTint = mClipQsScrim ? Color.BLACK : mSurfaceColor;
+ mBehindTint = mClipQsScrim ? mBackgroundColor : mSurfaceColor;
mNotifAlpha = mClipQsScrim ? mDefaultScrimAlpha : 0;
mNotifTint = Color.TRANSPARENT;
mFrontAlpha = 0f;
@@ -154,10 +155,10 @@ public enum ScrimState {
mBehindAlpha = mClipQsScrim ? 1 : mDefaultScrimAlpha;
mNotifAlpha = 1f;
mFrontAlpha = 0f;
- mBehindTint = mClipQsScrim ? Color.TRANSPARENT : Color.BLACK;
+ mBehindTint = mClipQsScrim ? Color.TRANSPARENT : mBackgroundColor;
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
},
@@ -184,11 +185,11 @@ public enum ScrimState {
final boolean isDocked = mDockManager.isDocked();
mBlankScreen = mDisplayRequiresBlanking;
- mFrontTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
mFrontAlpha = (alwaysOnEnabled || isDocked || quickPickupEnabled)
? mAodFrontScrimAlpha : 1f;
- mBehindTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
mBehindAlpha = ScrimController.TRANSPARENT;
mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
@@ -222,8 +223,8 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mFrontAlpha = mAodFrontScrimAlpha;
- mBehindTint = Color.BLACK;
- mFrontTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
+ mFrontTint = mBackgroundColor;
mBlankScreen = mDisplayRequiresBlanking;
mAnimationDuration = mWakeLockScreenSensorActive
? ScrimController.ANIMATION_DURATION_LONG : ScrimController.ANIMATION_DURATION;
@@ -231,7 +232,7 @@ public enum ScrimState {
@Override
public float getMaxLightRevealScrimAlpha() {
return mWakeLockScreenSensorActive ? ScrimController.WAKE_SENSOR_SCRIM_ALPHA
- : AOD.getMaxLightRevealScrimAlpha();
+ : AOD.getMaxLightRevealScrimAlpha();
}
},
@@ -245,7 +246,6 @@ public enum ScrimState {
mBehindAlpha = mClipQsScrim ? 1 : 0;
mNotifAlpha = 0;
mFrontAlpha = 0;
-
mAnimationDuration = mKeyguardFadingAway
? mKeyguardFadingAwayDuration
: CentralSurfaces.FADE_KEYGUARD_DURATION;
@@ -259,22 +259,22 @@ public enum ScrimState {
&& !fromAod;
mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.BLACK;
+ mBehindTint = mBackgroundColor;
mBlankScreen = false;
if (mDisplayRequiresBlanking && previousState == ScrimState.AOD) {
// Set all scrims black, before they fade transparent.
- updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimInFront, 1f /* alpha */, mBackgroundColor /* tint */);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor /* tint */);
// Scrims should still be black at the end of the transition.
- mFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
+ mFrontTint = mBackgroundColor;
+ mBehindTint = mBackgroundColor;
mBlankScreen = true;
}
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
},
@@ -283,8 +283,8 @@ public enum ScrimState {
@Override
public void prepare(ScrimState previousState) {
mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.BLACK;
- mNotifTint = mClipQsScrim ? Color.BLACK : Color.TRANSPARENT;
+ mBehindTint = mBackgroundColor;
+ mNotifTint = mClipQsScrim ? mBackgroundColor : Color.TRANSPARENT;
mFrontAlpha = 0;
mBehindAlpha = mClipQsScrim ? 1 : 0;
@@ -293,7 +293,7 @@ public enum ScrimState {
mBlankScreen = false;
if (mClipQsScrim) {
- updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, mBackgroundColor);
}
}
};
@@ -327,9 +327,11 @@ public enum ScrimState {
boolean mKeyguardFadingAway;
long mKeyguardFadingAwayDuration;
boolean mClipQsScrim;
+ int mBackgroundColor;
public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters,
DockManager dockManager) {
+ mBackgroundColor = scrimBehind.getContext().getColor(R.color.shade_scrim_background_dark);
mScrimInFront = scrimInFront;
mScrimBehind = scrimBehind;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 88347ab90606..4c83ca28b3cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -69,6 +69,7 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardWmStateRefactor;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
@@ -474,7 +475,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mIsDocked = mDockManager.isDocked();
}
- if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
// Show the keyguard views whenever we've told WM that the lockscreen is visible.
mShadeViewController.postToView(() ->
collectFlow(
@@ -1428,7 +1429,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
executeAfterKeyguardGoneAction();
}
- if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ if (KeyguardWmStateRefactor.isEnabled()) {
mKeyguardTransitionInteractor.startDismissKeyguardTransition();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 20d1fff91443..a2d8d1579e3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -28,6 +28,8 @@ import android.icu.lang.UCharacter;
import android.icu.text.DateTimePatternGenerator;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Parcelable;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -48,11 +50,11 @@ import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
-import com.android.systemui.res.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.demomode.DemoModeCommandReceiver;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -106,6 +108,7 @@ public class Clock extends TextView implements
private final int mAmPmStyle;
private boolean mShowSeconds;
private Handler mSecondsHandler;
+ private HandlerThread mHandlerThread;
// Fields to cache the width so the clock remains at an approximately constant width
private int mCharsAtCurrentWidth = -1;
@@ -146,6 +149,8 @@ public class Clock extends TextView implements
}
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
mUserTracker = Dependency.get(UserTracker.class);
+ mHandlerThread = new HandlerThread("Clock");
+ mHandlerThread.start();
setIncludeFontPadding(false);
}
@@ -205,7 +210,8 @@ public class Clock extends TextView implements
Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_HIDE_LIST);
mCommandQueue.addCallback(this);
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mCurrentUserId = mUserTracker.getUserId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index b7d8ee3943e3..a7440d6c200e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -21,6 +21,8 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.UserHandle;
import androidx.annotation.NonNull;
@@ -51,6 +53,7 @@ public class NextAlarmControllerImpl extends BroadcastReceiver
private final UserTracker mUserTracker;
private AlarmManager mAlarmManager;
private AlarmManager.AlarmClockInfo mNextAlarm;
+ private HandlerThread mHandlerThread;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -75,7 +78,10 @@ public class NextAlarmControllerImpl extends BroadcastReceiver
IntentFilter filter = new IntentFilter();
filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
broadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mHandlerThread = new HandlerThread("NextAlarmControllerImpl");
+ mHandlerThread.start();
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
updateNextAlarm();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 9f4a90658b2e..6a6efbc11362 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -157,7 +157,7 @@ public class SecurityControllerImpl implements SecurityController {
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
onUserSwitched(mUserTracker.getUserId());
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, mBgExecutor);
}
public void dump(PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 2ed9d1548007..0bc0e88114a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -36,9 +36,9 @@ import androidx.annotation.NonNull;
import com.android.internal.util.UserIcons;
import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import java.util.ArrayList;
@@ -66,11 +66,11 @@ public class UserInfoControllerImpl implements UserInfoController {
/**
*/
@Inject
- public UserInfoControllerImpl(Context context, @Main Executor mainExecutor,
+ public UserInfoControllerImpl(Context context, @Background Executor bgExecutor,
UserTracker userTracker) {
mContext = context;
mUserTracker = userTracker;
- mUserTracker.addCallback(mUserChangedCallback, mainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, bgExecutor);
IntentFilter profileFilter = new IntentFilter();
profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index df210b073e77..f0b49307aad5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -29,6 +29,7 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -81,6 +82,7 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
private volatile int mZenMode;
private long mZenUpdateTime;
private NotificationManager.Policy mConsolidatedNotificationPolicy;
+ private HandlerThread mHandlerThread;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@@ -133,6 +135,8 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
}
}
};
+ mHandlerThread = new HandlerThread("ZenModeControllerImpl");
+ mHandlerThread.start();
mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
updateZenMode(getModeSettingValueFromProvider());
@@ -143,7 +147,8 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable {
mSetupObserver = new SetupObserver(handler);
mSetupObserver.register();
mUserManager = context.getSystemService(UserManager.class);
- mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(handler));
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
// This registers the alarm broadcast receiver for the current user
mUserChangedCallback.onUserChanged(getCurrentUser(), context);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2b9ad50c1257..77518db9184c 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -480,7 +480,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
return;
}
- mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserTrackerCallback, mBgExecutor);
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 550a65c01bfc..f5b4d17ae7d3 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -26,6 +26,7 @@ import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserManager;
import android.provider.Settings;
@@ -38,11 +39,11 @@ import androidx.annotation.WorkerThread;
import com.android.internal.util.ArrayUtils;
import com.android.systemui.DejankUtils;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -98,6 +99,7 @@ public class TunerServiceImpl extends TunerService {
private UserTracker.Callback mCurrentUserTracker;
private UserTracker mUserTracker;
private final ComponentName mTunerComponent;
+ private HandlerThread mHandlerThread;
/**
*/
@@ -117,7 +119,8 @@ public class TunerServiceImpl extends TunerService {
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mTunerComponent = new ComponentName(mContext, TunerActivity.class);
-
+ mHandlerThread = new HandlerThread("TunerServiceImpl");
+ mHandlerThread.start();
for (UserInfo user : UserManager.get(mContext).getUsers()) {
mCurrentUser = user.getUserHandle().getIdentifier();
if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) {
@@ -135,7 +138,7 @@ public class TunerServiceImpl extends TunerService {
}
};
mUserTracker.addCallback(mCurrentUserTracker,
- new HandlerExecutor(mainHandler));
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index cf76c0d2e696..74e133923378 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -190,7 +190,7 @@ constructor(
}
}
- tracker.addCallback(callback, mainDispatcher.asExecutor())
+ tracker.addCallback(callback, backgroundDispatcher.asExecutor())
send(currentSelectionStatus)
awaitClose { tracker.removeCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
new file mode 100644
index 000000000000..693a835e25d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+
+object BooleanFlowOperators {
+ /**
+ * Logical AND operator for boolean flows. Will collect all flows and [combine] them to
+ * determine the result.
+ *
+ * Usage:
+ * ```
+ * val result = and(flow1, flow2)
+ * ```
+ */
+ fun and(vararg flows: Flow<Boolean>): Flow<Boolean> =
+ combine(flows.asIterable()) { values -> values.all { it } }
+
+ /**
+ * Logical NOT operator for a boolean flow.
+ *
+ * Usage:
+ * ```
+ * val negatedFlow = not(flow)
+ * ```
+ */
+ fun not(flow: Flow<Boolean>) = flow.map { !it }
+
+ /**
+ * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to
+ * determine the result.
+ */
+ fun or(vararg flows: Flow<Boolean>): Flow<Boolean> =
+ combine(flows.asIterable()) { values -> values.any { it } }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
index c587f2edd601..5150389930a9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -17,6 +17,7 @@
package com.android.systemui.util.kotlin
import dagger.Lazy
+import java.util.Optional
import kotlin.reflect.KProperty
/**
@@ -30,3 +31,16 @@ import kotlin.reflect.KProperty
* ```
*/
operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
+
+/**
+ * Extension operator that allows developers to use [java.util.Optional] as a nullable property
+ * delegate:
+ * ```kotlin
+ * class MyClass @Inject constructor(
+ * optionalDependency: Optional<Foo>,
+ * ) {
+ * val dependency: Foo? by optionalDependency
+ * }
+ * ```
+ */
+operator fun <T> Optional<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = getOrNull()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
new file mode 100644
index 000000000000..8d5e55a2917e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dagger
+
+import android.media.AudioManager
+import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+
+/** Dagger module for audio code in the volume package */
+@Module
+interface AudioModule {
+
+ companion object {
+
+ @Provides
+ fun provideAudioRepository(
+ audioManager: AudioManager,
+ @Background coroutineContext: CoroutineContext,
+ @Application coroutineScope: CoroutineScope,
+ ): AudioRepository = AudioRepositoryImpl(audioManager, coroutineContext, coroutineScope)
+
+ @Provides
+ fun provideAudioModeInteractor(repository: AudioRepository): AudioModeInteractor =
+ AudioModeInteractor(repository)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index b1bfbe0016e1..c842e5f295b9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -60,6 +60,9 @@ import kotlinx.coroutines.CoroutineScope;
/** Dagger Module for code in the volume package. */
@Module(
+ includes = {
+ AudioModule.class,
+ },
subcomponents = {
VolumePanelComponent.class
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 1e801aeb5a29..7c6ad233d853 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -32,6 +32,8 @@ import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
+import android.os.HandlerExecutor;
+import android.os.HandlerThread;
import android.os.IBinder;
import android.util.Log;
import android.view.Display;
@@ -125,6 +127,7 @@ public final class WMShell implements
private final DisplayTracker mDisplayTracker;
private final NoteTaskInitializer mNoteTaskInitializer;
private final Executor mSysUiMainExecutor;
+ private HandlerThread mHandlerThread;
// Listeners and callbacks. Note that we prefer member variable over anonymous class here to
// avoid the situation that some implementations, like KeyguardUpdateMonitor, use WeakReference
@@ -206,6 +209,8 @@ public final class WMShell implements
mDisplayTracker = displayTracker;
mNoteTaskInitializer = noteTaskInitializer;
mSysUiMainExecutor = sysUiMainExecutor;
+ mHandlerThread = new HandlerThread("WMShell");
+ mHandlerThread.start();
}
@Override
@@ -219,7 +224,8 @@ public final class WMShell implements
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
// Subscribe to user changes
- mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ mUserTracker.addCallback(mUserChangedCallback,
+ new HandlerExecutor(mHandlerThread.getThreadHandler()));
mCommandQueue.addCallback(this);
mPipOptional.ifPresent(this::initPip);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d06457ba86ab..be06cc5d3d1d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -121,7 +121,6 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
import com.android.settingslib.fuelgauge.BatteryStatus;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -930,7 +929,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void trustAgentHasTrust() {
// WHEN user has trust
- givenSelectedUserCanSkipBouncerFromTrustedState();
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
// THEN user is considered as "having trust" and bouncer can be skipped
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -954,7 +954,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void trustAgentHasTrust_fingerprintLockout() {
// GIVEN user has trust
- givenSelectedUserCanSkipBouncerFromTrustedState();
+ mKeyguardUpdateMonitor.onTrustChanged(true, true,
+ mSelectedUserInteractor.getSelectedUserId(), 0, null);
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
mSelectedUserInteractor.getSelectedUserId()));
@@ -2015,43 +2016,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
- mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
-
- // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
- // will trigger updateBiometricListeningState();
- clearInvocations(mFingerprintManager);
- mKeyguardUpdateMonitor.resetBiometricListeningState();
-
- // GIVEN the user can skip the bouncer
- givenSelectedUserCanSkipBouncerFromTrustedState();
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
- mTestableLooper.processAllMessages();
-
- // WHEN verify authenticate runs
- verifyFingerprintAuthenticateCall();
- }
-
- @Test
- public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
- mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
- // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
- // will trigger updateBiometricListeningState();
- clearInvocations(mFingerprintManager);
- mKeyguardUpdateMonitor.resetBiometricListeningState();
-
- // GIVEN the user can skip the bouncer
- givenSelectedUserCanSkipBouncerFromTrustedState();
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
- mTestableLooper.processAllMessages();
-
- // WHEN verify detect runs
- verifyFingerprintDetectCall();
- }
-
- @Test
public void testFingerprintSensorProperties() throws RemoteException {
mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
new ArrayList<>());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 9bcab57bec87..90878169c201 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -16,10 +16,12 @@
package com.android.systemui.accessibility.floatingmenu;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.annotation.NonNull;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -27,10 +29,12 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import org.junit.Before;
import org.junit.Rule;
@@ -46,6 +50,7 @@ import org.mockito.junit.MockitoRule;
@TestableLooper.RunWithLooper
public class DragToInteractAnimationControllerTest extends SysuiTestCase {
private DragToInteractAnimationController mDragToInteractAnimationController;
+ private DragToInteractView mInteractView;
private DismissView mDismissView;
@Rule
@@ -57,29 +62,72 @@ public class DragToInteractAnimationControllerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
+ final SecureSettings mockSecureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ mockSecureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
- final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
- stubMenuViewAppearance);
+ final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
+ stubMenuViewAppearance, mockSecureSettings));
+ mInteractView = spy(new DragToInteractView(mContext));
mDismissView = spy(new DismissView(mContext));
- DismissViewUtils.setup(mDismissView);
- mDragToInteractAnimationController = new DragToInteractAnimationController(
- mDismissView, stubMenuView);
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mInteractView, stubMenuView);
+ } else {
+ mDragToInteractAnimationController = new DragToInteractAnimationController(
+ mDismissView, stubMenuView);
+ }
+
+ mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
+ @Override
+ public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+ }
+
+ @Override
+ public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+ float velX, float velY, boolean wasFlungOut) {
+
+ }
+
+ @Override
+ public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+
+ }
+ });
}
@Test
- public void showDismissView_success() {
- mDragToInteractAnimationController.showDismissView(true);
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void showDismissView_success_old() {
+ mDragToInteractAnimationController.showInteractView(true);
verify(mDismissView).show();
}
@Test
- public void hideDismissView_success() {
- mDragToInteractAnimationController.showDismissView(false);
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void hideDismissView_success_old() {
+ mDragToInteractAnimationController.showInteractView(false);
verify(mDismissView).hide();
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void showDismissView_success() {
+ mDragToInteractAnimationController.showInteractView(true);
+
+ verify(mInteractView).show();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void hideDismissView_success() {
+ mDragToInteractAnimationController.showInteractView(false);
+
+ verify(mInteractView).hide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 215f93d1e163..e0df1e0e5586 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -42,6 +43,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
@@ -79,10 +81,12 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
stubWindowManager);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ secureSettings));
mViewPropertyAnimator = spy(mMenuView.animate());
doReturn(mViewPropertyAnimator).when(mMenuView).animate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index 9c8de302c5e1..c2ed7d4a9d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -22,10 +22,13 @@ import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTIO
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -37,7 +40,9 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerViewAccessibilityDelegate;
import androidx.test.filters.SmallTest;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
@@ -49,6 +54,8 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.concurrent.atomic.AtomicBoolean;
+
/** Tests for {@link MenuItemAccessibilityDelegate}. */
@SmallTest
@TestableLooper.RunWithLooper
@@ -59,17 +66,16 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Mock
private AccessibilityManager mAccessibilityManager;
- @Mock
- private SecureSettings mSecureSettings;
- @Mock
- private DragToInteractAnimationController.DismissCallback mStubDismissCallback;
-
+ private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private RecyclerView mStubListView;
private MenuView mMenuView;
+ private MenuViewLayer mMenuViewLayer;
private MenuItemAccessibilityDelegate mMenuItemAccessibilityDelegate;
private MenuAnimationController mMenuAnimationController;
private final Rect mDraggableBounds = new Rect(100, 200, 300, 400);
+ private final AtomicBoolean mEditReceived = new AtomicBoolean(false);
+
@Before
public void setUp() {
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
@@ -80,20 +86,28 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
final int halfScreenHeight =
stubWindowManager.getCurrentWindowMetrics().getBounds().height() / 2;
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
+ mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ mSecureSettings));
mMenuView.setTranslationY(halfScreenHeight);
+ mMenuViewLayer = spy(new MenuViewLayer(
+ mContext, stubWindowManager, mAccessibilityManager,
+ stubMenuViewModel, stubMenuViewAppearance, mMenuView,
+ mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+
doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
mStubListView = new RecyclerView(mContext);
mMenuAnimationController = spy(new MenuAnimationController(mMenuView,
stubMenuViewAppearance));
mMenuItemAccessibilityDelegate =
new MenuItemAccessibilityDelegate(new RecyclerViewAccessibilityDelegate(
- mStubListView), mMenuAnimationController);
+ mStubListView), mMenuAnimationController, mMenuViewLayer);
+ mEditReceived.set(false);
}
@Test
- public void getAccessibilityActionList_matchSize() {
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void getAccessibilityActionList_matchSize_withoutEdit() {
final AccessibilityNodeInfoCompat info =
new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
@@ -103,6 +117,17 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void getAccessibilityActionList_matchSize() {
+ final AccessibilityNodeInfoCompat info =
+ new AccessibilityNodeInfoCompat(new AccessibilityNodeInfo());
+
+ mMenuItemAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mStubListView, info);
+
+ assertThat(info.getActionList().size()).isEqualTo(7);
+ }
+
+ @Test
public void performMoveTopLeftAction_matchPosition() {
final boolean moveTopLeftAction =
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
@@ -169,13 +194,22 @@ public class MenuItemAccessibilityDelegateTest extends SysuiTestCase {
@Test
public void performRemoveMenuAction_success() {
- mMenuAnimationController.setDismissCallback(mStubDismissCallback);
final boolean removeMenuAction =
mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
R.id.action_remove_menu, null);
assertThat(removeMenuAction).isTrue();
- verify(mMenuAnimationController).removeMenu();
+ verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_remove_menu);
+ }
+
+ @Test
+ public void performEditAction_success() {
+ final boolean editAction =
+ mMenuItemAccessibilityDelegate.performAccessibilityAction(mStubListView,
+ R.id.action_edit, null);
+
+ assertThat(editAction).isTrue();
+ verify(mMenuViewLayer).dispatchAccessibilityAction(R.id.action_edit);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index e1522f5f6751..9e8c6b3395e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.floatingmenu;
+import static android.R.id.empty;
import static android.view.View.OVER_SCROLL_NEVER;
import static com.google.common.truth.Truth.assertThat;
@@ -27,6 +28,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
@@ -38,10 +41,11 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.test.filters.SmallTest;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.MotionEventHelper;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.common.bubbles.DismissView;
import org.junit.After;
@@ -71,6 +75,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
private DragToInteractAnimationController mDragToInteractAnimationController;
private RecyclerView mStubListView;
private DismissView mDismissView;
+ private DragToInteractView mInteractView;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@@ -81,19 +86,28 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
windowManager);
- mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
+ mStubMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance,
+ secureSettings);
mStubMenuView.setTranslationX(0);
mStubMenuView.setTranslationY(0);
mMenuAnimationController = spy(new MenuAnimationController(
mStubMenuView, stubMenuViewAppearance));
+ mInteractView = spy(new DragToInteractView(mContext));
mDismissView = spy(new DismissView(mContext));
- DismissViewUtils.setup(mDismissView);
- mDragToInteractAnimationController =
- spy(new DragToInteractAnimationController(mDismissView, mStubMenuView));
+
+ if (Flags.floatingMenuDragToEdit()) {
+ mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+ mInteractView, mStubMenuView));
+ } else {
+ mDragToInteractAnimationController = spy(new DragToInteractAnimationController(
+ mDismissView, mStubMenuView));
+ }
+
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
mDragToInteractAnimationController);
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
@@ -115,7 +129,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
@Test
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
- doReturn(false).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
+ doReturn(empty).when(mDragToInteractAnimationController).maybeConsumeMoveMotionEvent(
any(MotionEvent.class));
final int offset = 100;
final MotionEvent stubDownEvent =
@@ -136,6 +150,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
public void onActionMoveEvent_shouldShowDismissView() {
final int offset = 100;
final MotionEvent stubDownEvent =
@@ -154,6 +169,25 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onActionMoveEvent_shouldShowInteractView() {
+ final int offset = 100;
+ final MotionEvent stubDownEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+ MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+ mStubMenuView.getTranslationY());
+ final MotionEvent stubMoveEvent =
+ mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
+ MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
+ mStubMenuView.getTranslationY() + offset);
+
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+ mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
+
+ verify(mInteractView).show();
+ }
+
+ @Test
public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
final int offset = 100;
final MotionEvent stubDownEvent =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index bc9a0a5484ac..4a1bdbcc9b48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -72,6 +73,8 @@ import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
+import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -81,6 +84,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
@@ -122,18 +126,17 @@ public class MenuViewLayerTest extends SysuiTestCase {
private SysuiTestableContext mSpyContext = getContext();
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
-
- @Mock
- private SecureSettings mSecureSettings;
-
@Mock
private WindowManager mStubWindowManager;
-
@Mock
private AccessibilityManager mStubAccessibilityManager;
+ private final SecureSettings mSecureSettings = TestUtils.mockSecureSettings();
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+ private final ArgumentMatcher<IntentFilter> mNotificationMatcher =
+ (arg) -> arg.hasAction(ACTION_UNDO) && arg.hasAction(ACTION_DELETE);
+
@Before
public void setUp() throws Exception {
mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
@@ -145,8 +148,16 @@ public class MenuViewLayerTest extends SysuiTestCase {
new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
- mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager,
- mStubAccessibilityManager, mFloatingMenu, mSecureSettings);
+ MenuViewModel menuViewModel = new MenuViewModel(
+ mSpyContext, mStubAccessibilityManager, mSecureSettings);
+ MenuViewAppearance menuViewAppearance = new MenuViewAppearance(
+ mSpyContext, mStubWindowManager);
+ mMenuView = spy(
+ new MenuView(mSpyContext, menuViewModel, menuViewAppearance, mSecureSettings));
+
+ mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
+ mStubAccessibilityManager, menuViewModel, menuViewAppearance, mMenuView,
+ mFloatingMenu, mSecureSettings));
mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
@@ -236,6 +247,27 @@ public class MenuViewLayerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void onEditAction_gotoEditScreen_isCalled() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_edit);
+ verify(mMenuView).gotoEditScreen();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onDismissAction_hideMenuAndShowNotification() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+ verify(mMenuViewLayer).hideMenuAndShowNotification();
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onDismissAction_hideMenuAndShowMessage() {
+ mMenuViewLayer.dispatchAccessibilityAction(R.id.action_remove_menu);
+ verify(mMenuViewLayer).hideMenuAndShowMessage();
+ }
+
+ @Test
public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
final float menuTop = STATUS_BAR_HEIGHT + 100;
mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
@@ -307,19 +339,13 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Test
@EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() {
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mMockNotificationManager).notify(
eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN),
any(Notification.class));
- ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(
- IntentFilter.class);
verify(mSpyContext).registerReceiver(
- any(BroadcastReceiver.class),
- intentFilterCaptor.capture(),
- anyInt());
- assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue();
- assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue();
+ any(BroadcastReceiver.class), argThat(mNotificationMatcher), anyInt());
}
@Test
@@ -327,10 +353,10 @@ public class MenuViewLayerTest extends SysuiTestCase {
public void receiveActionUndo_dismissNotificationAndMenuVisible() {
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
BroadcastReceiver.class);
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
- any(IntentFilter.class), anyInt());
+ argThat(mNotificationMatcher), anyInt());
broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO));
verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -344,10 +370,10 @@ public class MenuViewLayerTest extends SysuiTestCase {
public void receiveActionDelete_dismissNotificationAndHideMenu() {
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
BroadcastReceiver.class);
- dragMenuThenReleasedInTarget();
+ dragMenuThenReleasedInTarget(R.id.action_remove_menu);
verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
- any(IntentFilter.class), anyInt());
+ argThat(mNotificationMatcher), anyInt());
broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE));
verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
@@ -423,10 +449,12 @@ public class MenuViewLayerTest extends SysuiTestCase {
});
}
- private void dragMenuThenReleasedInTarget() {
+ private void dragMenuThenReleasedInTarget(int id) {
MagnetizedObject.MagnetListener magnetListener =
- mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener();
+ mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener(id);
+ View view = mock(View.class);
+ when(view.getId()).thenReturn(id);
magnetListener.onReleasedInTarget(
- new MagnetizedObject.MagneticTarget(mock(View.class), 200));
+ new MagnetizedObject.MagneticTarget(view, 200));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
index 8da6cf98d76f..7c97f53d539d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
@@ -17,15 +17,19 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.app.UiModeManager.MODE_NIGHT_YES;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
+
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.UiModeManager;
+import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.platform.test.annotations.EnableFlags;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
@@ -36,6 +40,8 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.Flags;
import com.android.systemui.Prefs;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.accessibility.utils.TestUtils;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.After;
@@ -65,17 +71,23 @@ public class MenuViewTest extends SysuiTestCase {
@Mock
private AccessibilityManager mAccessibilityManager;
+ private SysuiTestableContext mSpyContext;
+
@Before
public void setUp() throws Exception {
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mNightMode = mUiModeManager.getNightMode();
mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+ mSpyContext = spy(mContext);
+ final SecureSettings secureSettings = TestUtils.mockSecureSettings();
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext, mAccessibilityManager,
- mock(SecureSettings.class));
+ secureSettings);
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
- mStubMenuViewAppearance = new MenuViewAppearance(mContext, stubWindowManager);
- mMenuView = spy(new MenuView(mContext, stubMenuViewModel, mStubMenuViewAppearance));
- mLastPosition = Prefs.getString(mContext,
+ mStubMenuViewAppearance = new MenuViewAppearance(mSpyContext, stubWindowManager);
+ mMenuView = spy(new MenuView(mSpyContext, stubMenuViewModel, mStubMenuViewAppearance,
+ secureSettings));
+ mLastPosition = Prefs.getString(mSpyContext,
Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
}
@@ -154,6 +166,25 @@ public class MenuViewTest extends SysuiTestCase {
assertThat(radiiAnimator.isStarted()).isTrue();
}
+ @Test
+ public void getIntentForEditScreen_validate() {
+ Intent intent = mMenuView.getIntentForEditScreen();
+ String[] targets = intent.getBundleExtra(
+ ":settings:show_fragment_args").getStringArray("targets");
+
+ assertThat(intent.getAction()).isEqualTo(Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS);
+ assertThat(targets).asList().containsExactlyElementsIn(TestUtils.TEST_BUTTON_TARGETS);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_EDIT)
+ public void gotoEditScreen_sendsIntent() {
+ // Notably, this shouldn't crash the settings app,
+ // because the button target args are configured.
+ mMenuView.gotoEditScreen();
+ verify(mSpyContext).startActivity(any());
+ }
+
private InstantInsetLayerDrawable getMenuViewInsetLayer() {
return (InstantInsetLayerDrawable) mMenuView.getBackground();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
index 10c8caa4fd27..8399fa85bfb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/utils/TestUtils.java
@@ -16,11 +16,27 @@
package com.android.systemui.accessibility.utils;
+import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.util.settings.SecureSettings;
+import java.util.Set;
+import java.util.StringJoiner;
import java.util.function.BooleanSupplier;
public class TestUtils {
+ private static final ComponentName TEST_COMPONENT_A = new ComponentName("pkg", "A");
+ private static final ComponentName TEST_COMPONENT_B = new ComponentName("pkg", "B");
+ public static final String[] TEST_BUTTON_TARGETS = {
+ TEST_COMPONENT_A.flattenToString(), TEST_COMPONENT_B.flattenToString()};
public static long DEFAULT_CONDITION_DURATION = 5_000;
/**
@@ -55,4 +71,28 @@ public class TestUtils {
SystemClock.sleep(sleepMs);
}
}
+
+ /**
+ * Returns a mock secure settings configured to return information needed for tests.
+ * Currently, this only includes button targets.
+ */
+ public static SecureSettings mockSecureSettings() {
+ SecureSettings secureSettings = mock(SecureSettings.class);
+
+ final String targets = getShortcutTargets(
+ Set.of(TEST_COMPONENT_A, TEST_COMPONENT_B));
+ when(secureSettings.getStringForUser(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
+ UserHandle.USER_CURRENT)).thenReturn(targets);
+
+ return secureSettings;
+ }
+
+ private static String getShortcutTargets(Set<ComponentName> components) {
+ final StringJoiner stringJoiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+ for (ComponentName target : components) {
+ stringJoiner.add(target.flattenToString());
+ }
+ return stringJoiner.toString();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index e0c6bbad5635..0ba9abe2d36c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -32,12 +32,16 @@ import com.android.internal.statusbar.IStatusBarService
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.QuickSettingsController
import com.android.systemui.shade.ShadeController
@@ -59,7 +63,6 @@ import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Rule
@@ -76,7 +79,8 @@ import org.mockito.junit.MockitoJUnit
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class BackActionInteractorTest : SysuiTestCase() {
- private val testScope = TestScope()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
private val executor = FakeExecutor(FakeSystemClock())
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -105,6 +109,8 @@ class BackActionInteractorTest : SysuiTestCase() {
headsUpManager,
powerInteractor,
activeNotificationsInteractor,
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
}
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 a47e28801709..7c03d7899398 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -27,12 +27,13 @@ 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.Flags
import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
-import com.android.systemui.log.logcatLogBuffer
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -42,7 +43,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.leak.RotationUtils
import com.android.systemui.util.mockito.any
-import javax.inject.Provider
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -61,8 +62,10 @@ import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
+import javax.inject.Provider
+@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidTestingRunner::class)
class AuthRippleControllerTest : SysuiTestCase() {
@@ -74,6 +77,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
+ @Mock private lateinit var authRippleInteractor: AuthRippleInteractor
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock
private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@@ -88,8 +92,6 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
- private lateinit var featureFlags: FeatureFlags
- @Mock
private lateinit var lightRevealScrim: LightRevealScrim
@Mock
private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@@ -103,6 +105,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Before
fun setUp() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
MockitoAnnotations.initMocks(this)
staticMockSession = mockitoSession()
.mockStatic(RotationUtils::class.java)
@@ -128,6 +131,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
biometricUnlockController,
lightRevealScrim,
+ authRippleInteractor,
facePropertyRepository,
rippleView,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 54dbd04fb367..3603c3c6c46a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -63,14 +63,20 @@ import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -107,6 +113,8 @@ import org.mockito.junit.MockitoRule
@RunWith(JUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SideFpsOverlayViewBinderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@Mock private lateinit var displayManager: DisplayManager
@@ -237,7 +245,8 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
- mock(),
+ kosmos.biometricSettingsRepository,
+ kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
@@ -246,10 +255,12 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() {
mContext,
mock(),
sfpsSensorInteractor,
- mock(),
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
displayStateInteractor,
UnconfinedTestDispatcher(),
testScope.backgroundScope,
+ kosmos.powerInteractor,
)
viewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 3888f2b940b3..6a9c88151dd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -21,6 +21,7 @@ import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.drawable.BitmapDrawable
+import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
@@ -1225,6 +1226,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun descriptionOverriddenByContentView() =
runGenericTest(contentView = promptContentView, description = "test description") {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1235,6 +1237,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun descriptionWithoutContentView() =
runGenericTest(description = "test description") {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val contentView by collectLastValue(viewModel.contentView)
val description by collectLastValue(viewModel.description)
@@ -1244,6 +1247,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun defaultLogoIfNoLogoSet() = runGenericTest {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(defaultLogoIcon)
}
@@ -1251,6 +1255,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun logoResSetByApp() =
runGenericTest(logoRes = logoResFromApp) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
assertThat(logo).isEqualTo(logoFromApp)
}
@@ -1258,6 +1263,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Test
fun logoBitmapSetByApp() =
runGenericTest(logoBitmap = logoBitmapFromApp) {
+ mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
val logo by collectLastValue(viewModel.logo)
assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 1fa60fc9044b..3c430316ffbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -56,19 +56,27 @@ import com.android.systemui.bouncer.ui.BouncerView
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.display.data.repository.FakeDisplayRepository
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.concurrency.FakeExecutor
@@ -98,6 +106,7 @@ import org.mockito.junit.MockitoRule
@SmallTest
@RunWith(JUnit4::class)
class SideFpsOverlayViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@@ -239,19 +248,22 @@ class SideFpsOverlayViewModelTest : SysuiTestCase() {
windowManager,
displayStateInteractor,
Optional.of(fingerprintInteractiveToAuthProvider),
- mock(),
+ kosmos.biometricSettingsRepository,
+ kosmos.keyguardTransitionInteractor,
SideFpsLogger(logcatLogBuffer("SfpsLogger"))
)
sideFpsProgressBarViewModel =
SideFpsProgressBarViewModel(
mContext,
- mock(),
+ kosmos.deviceEntryFingerprintAuthInteractor,
sfpsSensorInteractor,
- mock(),
+ kosmos.dozeServiceHost,
+ kosmos.keyguardInteractor,
displayStateInteractor,
- StandardTestDispatcher(),
+ kosmos.testDispatcher,
testScope.backgroundScope,
+ kosmos.powerInteractor,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 0dfdeca60fcd..bdf0e06ce410 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.deviceentry.data.repository
+package com.android.systemui.deviceentry.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -23,12 +23,13 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
@@ -158,9 +159,10 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
}
private suspend fun enterDeviceFromBiometricUnlock() {
- kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock(
+ kosmos.fakeKeyguardRepository.setBiometricUnlockSource(
BiometricUnlockSource.FINGERPRINT_SENSOR
)
+ kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
}
private fun fingerprintFailure() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
new file mode 100644
index 000000000000..ed80a869dda8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepositoryImplTest.kt
@@ -0,0 +1,98 @@
+package com.android.systemui.keyboard.stickykeys.data.repository
+
+import android.content.pm.UserInfo
+import android.hardware.input.InputManager
+import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+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 StickyKeysRepositoryImplTest : SysuiTestCase() {
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private val secureSettings = FakeSettings()
+ private val userRepository = Kosmos().fakeUserRepository
+ private lateinit var stickyKeysRepository: StickyKeysRepositoryImpl
+
+ @Before
+ fun setup() {
+ stickyKeysRepository = StickyKeysRepositoryImpl(
+ mock<InputManager>(),
+ dispatcher,
+ secureSettings,
+ userRepository,
+ mock<StickyKeysLogger>()
+ )
+ userRepository.setUserInfos(USER_INFOS)
+ setStickyKeySettingForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
+ setStickyKeySettingForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForCurrentUser() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+
+ val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+
+ assertThat(enabled).isTrue()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsNewValueWhenSettingChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectValues(stickyKeysRepository.settingEnabled)
+ runCurrent()
+
+ setStickyKeySettingForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+
+ assertThat(enabled).containsExactly(true, false).inOrder()
+ }
+ }
+
+ @Test
+ fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ val enabled by collectLastValue(stickyKeysRepository.settingEnabled)
+ runCurrent()
+
+ userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+
+ assertThat(enabled).isFalse()
+ }
+ }
+
+ private fun setStickyKeySettingForUser(enabled: Boolean, userInfo: UserInfo) {
+ val newValue = if (enabled) "1" else "0"
+ secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, userInfo.id)
+ }
+
+ private companion object {
+ val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+ val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+ val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 8a713688acf9..6eebb6d19e5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyboard.stickykeys.ui.viewmodel
import android.hardware.input.InputManager
import android.hardware.input.StickyModifierState
+import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -31,8 +32,11 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -57,6 +61,8 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
private lateinit var viewModel: StickyKeysIndicatorViewModel
private val inputManager = mock<InputManager>()
private val keyboardRepository = FakeKeyboardRepository()
+ private val secureSettings = FakeSettings()
+ private val userRepository = Kosmos().fakeUserRepository
private val captor =
ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
@@ -65,8 +71,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
val stickyKeysRepository = StickyKeysRepositoryImpl(
inputManager,
dispatcher,
+ secureSettings,
+ userRepository,
mock<StickyKeysLogger>()
)
+ setStickyKeySetting(enabled = false)
viewModel =
StickyKeysIndicatorViewModel(
stickyKeysRepository = stickyKeysRepository,
@@ -76,13 +85,24 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
}
@Test
- fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() {
+ fun doesntListenToStickyKeysOnlyWhenKeyboardIsConnected() {
testScope.runTest {
collectLastValue(viewModel.indicatorContent)
+
+ keyboardRepository.setIsAnyKeyboardConnected(true)
runCurrent()
+
verifyZeroInteractions(inputManager)
+ }
+ }
+ @Test
+ fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnectedAndSettingIsOn() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeySetting(enabled = true)
runCurrent()
verify(inputManager)
@@ -93,11 +113,31 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
}
}
+ private fun setStickyKeySetting(enabled: Boolean) {
+ val newValue = if (enabled) "1" else "0"
+ val defaultUser = userRepository.getSelectedUserInfo().id
+ secureSettings.putStringForUser(ACCESSIBILITY_STICKY_KEYS, newValue, defaultUser)
+ }
+
+ @Test
+ fun stopsListeningToStickyKeysWhenStickyKeySettingsIsTurnedOff() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ setStickyKeysActive()
+ runCurrent()
+
+ setStickyKeySetting(enabled = false)
+ runCurrent()
+
+ verify(inputManager).unregisterStickyModifierStateListener(any())
+ }
+ }
+
@Test
fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
testScope.runTest {
collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
runCurrent()
keyboardRepository.setIsAnyKeyboardConnected(false)
@@ -111,7 +151,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun emitsStickyKeysListWhenStickyKeyIsPressed() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(ALT to false))
@@ -123,7 +163,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun emitsEmptyListWhenNoStickyKeysAreActive() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(emptyMap())
@@ -135,7 +175,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun passesAllStickyKeysToDialog() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(
ALT to false,
@@ -154,7 +194,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun showsOnlyLockedStateIfKeyIsStickyAndLocked() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(
ALT to false,
@@ -168,7 +208,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() {
testScope.runTest {
val stickyKeys by collectLastValue(viewModel.indicatorContent)
- keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeysActive()
setStickyKeys(mapOf(
META to false,
@@ -186,6 +226,11 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
}
}
+ private fun setStickyKeysActive() {
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ setStickyKeySetting(enabled = true)
+ }
+
private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
runCurrent()
verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8a3a4342915b..1183964c39d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -25,6 +25,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
+import static com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR;
import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -270,8 +271,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mSceneContainerFlags,
mKosmos::getCommunalInteractor);
mFeatureFlags = new FakeFeatureFlags();
- mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
+ mSetFlagsRule.disableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR);
DejankUtils.setImmediate(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 4f3a63dd2829..e93ad0be3e85 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
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
@@ -29,7 +30,6 @@ import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
@@ -137,8 +137,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
- featureFlags = FakeFeatureFlags().apply { set(Flags.KEYGUARD_WM_STATE_REFACTOR, false) }
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+ featureFlags = FakeFeatureFlags()
keyguardInteractor = createKeyguardInteractor()
@@ -299,6 +299,10 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
powerInteractor = powerInteractor,
)
.apply { start() }
+
+ mSetFlagsRule.disableFlags(
+ FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index c864704f6997..699284e29ce3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -71,6 +72,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
FakeFeatureFlagsClassic().apply { set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) }
underTest =
DefaultDeviceEntrySection(
+ TestScope().backgroundScope,
keyguardUpdateMonitor,
authController,
windowManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index f93d52b2c35c..aa54565c2aa0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -28,6 +28,7 @@ import android.view.MotionEvent.ACTION_UP
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.NavigationEdgeBackPlugin
@@ -40,8 +41,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -59,12 +62,16 @@ class BackPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var latencyTracker: LatencyTracker
+ @Mock private lateinit var interactionJankMonitor: InteractionJankMonitor
@Mock private lateinit var layoutParams: WindowManager.LayoutParams
@Mock private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ `when`(interactionJankMonitor.begin(any(), anyInt())).thenReturn(true)
+ `when`(interactionJankMonitor.end(anyInt())).thenReturn(true)
+ `when`(interactionJankMonitor.cancel(anyInt())).thenReturn(true)
mBackPanelController =
BackPanelController(
context,
@@ -74,6 +81,7 @@ class BackPanelControllerTest : SysuiTestCase() {
vibratorHelper,
configurationController,
latencyTracker,
+ interactionJankMonitor,
)
mBackPanelController.setLayoutParams(layoutParams)
mBackPanelController.setBackCallback(backCallback)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
index e4432f3038bc..0636831c7c66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -30,13 +30,11 @@ import android.os.UserHandle
import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
import android.testing.AndroidTestingRunner
-import android.view.View
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
-import com.android.systemui.animation.LaunchableView
import com.android.systemui.appops.AppOpsController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.logging.PrivacyLogger
@@ -206,10 +204,7 @@ class PrivacyDialogControllerV2Test : SysuiTestCase() {
@Test
fun testShowDialogShowsDialogWithView() {
val parent = LinearLayout(context)
- val view =
- object : View(context), LaunchableView {
- override fun setShouldBlockVisibilityChanges(block: Boolean) {}
- }
+ val view = OngoingPrivacyChip(context)
parent.addView(view)
val usage = createMockPermGroupUsage()
`when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
index fa02e8cb3e54..f98b68f2309b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt
@@ -173,7 +173,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
@@ -186,7 +186,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
captor.value.onClick(privacyChip)
verify(privacyDialogController).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
@@ -207,7 +207,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
captor.value.onClick(privacyChip)
verify(privacyDialogController, never()).showDialog(any(Context::class.java))
verify(privacyDialogControllerV2, never())
- .showDialog(any(Context::class.java), any(View::class.java))
+ .showDialog(any(Context::class.java), any(OngoingPrivacyChip::class.java))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
new file mode 100644
index 000000000000..e8aa8f0bdc5d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tileimpl
+
+import android.animation.AnimatorTestRule
+import android.content.Context
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import android.testing.UiThreadTest
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.widget.ImageView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+/** Test for regression b/311121830 */
+@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@SmallTest
+class QSIconViewImplTest_311121830 : SysuiTestCase() {
+
+ @get:Rule val animatorRule = AnimatorTestRule()
+
+ @Test
+ fun alwaysLastIcon() {
+ // Need to inflate with the correct theme so the colors can be retrieved and the animations
+ // are run
+ val iconView =
+ AnimateQSIconViewImpl(
+ ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
+ )
+
+ val initialState =
+ QSTile.State().apply {
+ state = Tile.STATE_INACTIVE
+ icon = QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_no_internet_available)
+ }
+ val firstState =
+ QSTile.State().apply {
+ state = Tile.STATE_ACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+ }
+ val secondState =
+ QSTile.State().apply {
+ state = Tile.STATE_ACTIVE
+ icon = QSTileImpl.ResourceIcon.get(WifiIcons.WIFI_FULL_ICONS[4])
+ }
+
+ // Start with the initial state
+ iconView.setIcon(initialState, /* allowAnimations= */ false)
+
+ // Set the first state to animate, and advance time to half the time of the animation
+ iconView.setIcon(firstState, /* allowAnimations= */ true)
+ animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH / 2)
+
+ // Set the second state to animate (it shouldn't, because `State.state` is the same) and
+ // advance time to 2 animations length
+ iconView.setIcon(secondState, /* allowAnimations= */ true)
+ animatorRule.advanceTimeBy(QSIconViewImpl.QS_ANIM_LENGTH * 2)
+
+ assertThat(iconView.mLastIcon).isEqualTo(secondState.icon)
+ }
+
+ private class AnimateQSIconViewImpl(context: Context) : QSIconViewImpl(context) {
+ override fun createIcon(): View {
+ return object : ImageView(context) {
+ override fun isShown(): Boolean {
+ return true
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index c7479fd50db1..1ed8c3cdf0ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -32,6 +32,7 @@ import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.recordissue.RecordIssueDialogDelegate
import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,6 +66,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
+ @Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
@Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@Mock private lateinit var dialog: SystemUIDialog
@@ -94,6 +96,7 @@ class RecordIssueTileTest : SysuiTestCase() {
keyguardDismissUtil,
keyguardStateController,
dialogLauncherAnimator,
+ userContextProvider,
delegateFactory,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index b24b8773d600..c0ef50fa9072 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -1069,6 +1069,22 @@ public class InternetDialogControllerTest extends SysuiTestCase {
assertThat(mInternetDialogController.mCallback).isNull();
}
+ @Test
+ public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() {
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{});
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubId()).isFalse();
+ }
+
+ @Test
+ public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() {
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged();
+
+ assertThat(mInternetDialogController.hasActiveSubId()).isTrue();
+ }
+
private String getResourcesString(String name) {
return mContext.getResources().getString(getResourcesId(name));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 70a48f574949..fdbba905d94c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.app.AssistUtils
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
@@ -109,6 +110,8 @@ class OverviewProxyServiceTest : SysuiTestCase() {
@Mock
private lateinit var unfoldTransitionProgressForwarder:
Optional<UnfoldTransitionProgressForwarder>
+ @Mock
+ private lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
@@ -131,7 +134,10 @@ class OverviewProxyServiceTest : SysuiTestCase() {
whenever(packageManager.resolveServiceAsUser(any(), anyInt(), anyInt()))
.thenReturn(mock(ResolveInfo::class.java))
- featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+ mSetFlagsRule.disableFlags(
+ com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
+
subject =
OverviewProxyService(
context,
@@ -155,7 +161,8 @@ class OverviewProxyServiceTest : SysuiTestCase() {
featureFlags,
FakeSceneContainerFlags(),
dumpManager,
- unfoldTransitionProgressForwarder
+ unfoldTransitionProgressForwarder,
+ broadcastDispatcher
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 45f68694e378..a6e240b1b701 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -22,6 +22,7 @@ import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.MotionEvent
import android.view.View
+import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalRepository
@@ -61,6 +62,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
@Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var powerManager: PowerManager
+ private lateinit var parentView: FrameLayout
private lateinit var containerView: View
private lateinit var testableLooper: TestableLooper
@@ -225,15 +227,38 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
}
+ @Test
+ fun onTouchEvent_containerViewDisposed_doesNotIntercept() {
+ // Communal is open.
+ communalRepository.setDesiredScene(CommunalSceneKey.Communal)
+
+ initAndAttachContainerView()
+ testableLooper.processAllMessages()
+
+ // Touch events are intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+
+ // Container view disposed.
+ underTest.disposeView()
+
+ // Touch events are not intercepted.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ }
+
private fun initAndAttachContainerView() {
containerView = View(context)
+
+ parentView = FrameLayout(context)
+ parentView.addView(containerView)
+
// Make view clickable so that dispatchTouchEvent returns true.
containerView.isClickable = true
underTest.initView(containerView)
// Attach the view so that flows start collecting.
- ViewUtils.attachView(containerView)
+ ViewUtils.attachView(parentView)
// Give the view a size so that determining if a touch starts at the right edge works.
+ parentView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
containerView.layout(0, 0, CONTAINER_WIDTH, CONTAINER_HEIGHT)
}
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 a11839c56b0f..6681ceee3d09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -74,10 +74,12 @@ import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -480,6 +482,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/321332798")
fun setsUpCommunalHubLayout_whenFlagEnabled() {
if (!isComposeAvailable()) {
return
@@ -511,6 +514,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
}
whenever(mGlanceableHubContainerController.isEnabled()).thenReturn(false)
+ whenever(mGlanceableHubContainerController.enabledState())
+ .thenReturn(MutableStateFlow(false))
val mockCommunalPlaceholder = mock(View::class.java)
val fakeViewIndex = 20
@@ -520,8 +525,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
underTest.setupCommunalHubLayout()
- // No adding or removing of views occurs.
- verify(view, times(0)).removeView(mockCommunalPlaceholder)
+ // No adding of views occurs.
verify(view, times(0)).addView(any(), eq(fakeViewIndex))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index c4911a41b4a7..cc79ca4efaa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,15 +22,18 @@ import android.view.Display
import android.view.WindowManager
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
-import com.android.keyguard.TestScopeProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.assist.AssistManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
@@ -64,6 +67,8 @@ class ShadeControllerImplTest : SysuiTestCase() {
private val executor = FakeExecutor(FakeSystemClock())
private val testDispatcher = StandardTestDispatcher()
private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -84,12 +89,14 @@ class ShadeControllerImplTest : SysuiTestCase() {
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
WindowRootViewVisibilityInteractor(
- TestScopeProvider.getTestScope(),
+ testScope,
WindowRootViewVisibilityRepository(iStatusBarService, executor),
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
- ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+ kosmos.sceneContainerFlags,
+ kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 65697b736cbd..36f643ab9cca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -24,7 +24,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -53,8 +52,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -78,28 +77,22 @@ class ConversationCoordinatorTest : SysuiTestCase() {
private lateinit var coordinator: ConversationCoordinator
- private val featureFlags = FakeFeatureFlagsClassic()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- coordinator = ConversationCoordinator(
- peopleNotificationIdentifier,
- conversationIconManager,
- HighPriorityProvider(
+ coordinator =
+ ConversationCoordinator(
peopleNotificationIdentifier,
- GroupMembershipManagerImpl(featureFlags)
- ),
- headerController
- )
+ conversationIconManager,
+ HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
+ headerController
+ )
whenever(channel.isImportantConversation).thenReturn(true)
coordinator.attach(pipeline)
// capture arguments:
- promoter = withArgCaptor {
- verify(pipeline).addPromoter(capture())
- }
+ promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) }
beforeRenderListListener = withArgCaptor {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
@@ -111,10 +104,10 @@ class ConversationCoordinatorTest : SysuiTestCase() {
entry = NotificationEntryBuilder().setChannel(channel).build()
val section = NotifSection(peopleAlertingSectioner, 0)
- entryA = NotificationEntryBuilder().setChannel(channel)
- .setSection(section).setTag("A").build()
- entryB = NotificationEntryBuilder().setChannel(channel)
- .setSection(section).setTag("B").build()
+ entryA =
+ NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build()
+ entryB =
+ NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build()
}
@Test
@@ -129,11 +122,12 @@ class ConversationCoordinatorTest : SysuiTestCase() {
val altChildA = NotificationEntryBuilder().setTag("A").build()
val altChildB = NotificationEntryBuilder().setTag("B").build()
val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build()
- val groupEntry = GroupEntryBuilder()
- .setParent(GroupEntry.ROOT_ENTRY)
- .setSummary(summary)
- .setChildren(listOf(entry, altChildA, altChildB))
- .build()
+ val groupEntry =
+ GroupEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setSummary(summary)
+ .setChildren(listOf(entry, altChildA, altChildB))
+ .build()
assertTrue(promoter.shouldPromoteToTopLevel(entry))
assertFalse(promoter.shouldPromoteToTopLevel(altChildA))
assertFalse(promoter.shouldPromoteToTopLevel(altChildB))
@@ -146,41 +140,42 @@ class ConversationCoordinatorTest : SysuiTestCase() {
@Test
fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
// GIVEN
- val alertingEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_DEFAULT).build()
+ val alertingEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// put alerting people notifications in this section
assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
- }
+ }
@Test
fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
// GIVEN
- val silentEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_LOW).build()
+ val silentEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// THEN put silent people notifications in this section
assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
// People Alerting sectioning happens before the silent one.
- // It claims high important conversations and rest of conversations will be considered as silent.
+ // It claims high important conversations and rest of conversations will be considered as
+ // silent.
assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
}
@Test
fun testNotInPeopleSection() {
// GIVEN
- val entry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_LOW).build()
- val importantEntry = NotificationEntryBuilder().setChannel(channel)
- .setImportance(IMPORTANCE_HIGH).build()
+ val entry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build()
+ val importantEntry =
+ NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
- .thenReturn(TYPE_NON_PERSON)
+ .thenReturn(TYPE_NON_PERSON)
whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
- .thenReturn(TYPE_NON_PERSON)
+ .thenReturn(TYPE_NON_PERSON)
// THEN - only put people notification either silent or alerting
assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
@@ -190,19 +185,23 @@ class ConversationCoordinatorTest : SysuiTestCase() {
@Test
fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() {
// GIVEN
- val altChildA = NotificationEntryBuilder().setTag("A")
- .setImportance(IMPORTANCE_DEFAULT).build()
- val altChildB = NotificationEntryBuilder().setTag("B")
- .setImportance(IMPORTANCE_LOW).build()
- val summary = NotificationEntryBuilder().setId(2)
- .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
- val groupEntry = GroupEntryBuilder()
+ val altChildA =
+ NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build()
+ val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build()
+ val summary =
+ NotificationEntryBuilder()
+ .setId(2)
+ .setImportance(IMPORTANCE_LOW)
+ .setChannel(channel)
+ .build()
+ val groupEntry =
+ GroupEntryBuilder()
.setParent(GroupEntry.ROOT_ENTRY)
.setSummary(summary)
.setChildren(listOf(altChildA, altChildB))
.build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_PERSON)
// THEN
assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index 58eec2e71d32..4519ba6d3590 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -65,6 +65,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
import com.android.systemui.util.settings.SecureSettings;
@@ -111,6 +112,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
private final SectionStyleProvider mSectionStyleProvider = new SectionStyleProvider();
@Mock private UserTracker mUserTracker;
+ @Mock private GroupMembershipManager mGroupMembershipManager;
private NotifUiAdjustmentProvider mAdjustmentProvider;
@@ -127,7 +129,9 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mSecureSettings,
mLockscreenUserManager,
mSectionStyleProvider,
- mUserTracker);
+ mUserTracker,
+ mGroupMembershipManager
+ );
mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
mInflationError = new Exception(TEST_MESSAGE);
mErrorManager = new NotifInflationErrorManager();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index f9f8d8a2cfc6..73c49c023dd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.collection.inflation
import android.database.ContentObserver
import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
@@ -28,6 +30,8 @@ import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
@@ -35,6 +39,8 @@ import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -55,6 +61,7 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
private val uri = FakeSettings().getUriFor(SHOW_NOTIFICATION_SNOOZE)
private val dirtyListener: Runnable = mock()
private val userTracker: UserTracker = mock()
+ private val groupMembershipManager: GroupMembershipManager = mock()
private val section = NotifSection(mock(), 0)
private val entry = NotificationEntryBuilder()
@@ -69,7 +76,8 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
secureSettings,
lockscreenUserManager,
sectionStyleProvider,
- userTracker
+ userTracker,
+ groupMembershipManager,
)
@Before
@@ -127,4 +135,42 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
assertThat(withSnoozing.isSnoozeEnabled).isTrue()
assertThat(withSnoozing).isNotEqualTo(original)
}
+
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun changeIsChildInGroup_asyncHybirdFlagEnabled_needReInflation() {
+ // Given: an Entry that is not child in group
+ // AsyncHybridViewInflation flag is enabled
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+ val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+ // When: the Entry becomes a group child
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(newAdjustment.isChildInGroup).isTrue()
+ assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+ // Then: need re-inflation
+ assertTrue(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
+
+ @Test
+ @DisableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun changeIsChildInGroup_asyncHybirdFlagDisabled_noNeedForReInflation() {
+ // Given: an Entry that is not child in group
+ // AsyncHybridViewInflation flag is disabled
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(false)
+ val oldAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(oldAdjustment.isChildInGroup).isFalse()
+
+ // When: the Entry becomes a group child
+ whenever(groupMembershipManager.isChildInGroup(entry)).thenReturn(true)
+ val newAdjustment = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(newAdjustment.isChildInGroup).isTrue()
+ assertThat(newAdjustment).isNotEqualTo(oldAdjustment)
+
+ // Then: need no re-inflation
+ assertFalse(NotifUiAdjustment.needReinflate(oldAdjustment, newAdjustment))
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index ff02ef3d4e62..b01281c4daa3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -42,9 +42,9 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository;
@@ -110,7 +110,8 @@ public class NotificationLoggerTest extends SysuiTestCase {
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
new NotificationPanelLoggerFake();
- private final TestScope mTestScope = TestScopeProvider.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
@@ -133,7 +134,9 @@ public class NotificationLoggerTest extends SysuiTestCase {
mKeyguardRepository,
mHeadsUpManager,
mPowerInteractor,
- mActiveNotificationsInteractor);
+ mActiveNotificationsInteractor,
+ mKosmos.getFakeSceneContainerFlags(),
+ () -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
index 3f7fc979b1e3..fd4192151c57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt
@@ -62,7 +62,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
fun onCreateView_noMatchingViewForName_returnNull() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate an ImageView for the expanded layout
val createdView = inflaterFactory.onCreateView("ImageView", context, attrs)
@@ -78,7 +78,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
fun onCreateView_noMatchingViewForLayoutType_returnNull() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_HEADS_UP
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate a TextView for the heads-up layout
val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -94,7 +94,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
fun onCreateView_matchingViews_returnReplacementView() {
// GIVEN we have ViewFactories that replaces TextViews in expanded and collapsed layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
- inflaterFactory = NotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
+ inflaterFactory = createNotifLayoutInflaterFactory(row, layoutType, viewFactorySpies)
// WHEN we try to inflate a TextView for the expanded layout
val createdView = inflaterFactory.onCreateView("TextView", context, attrs)
@@ -110,7 +110,7 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
// GIVEN we have two factories that replaces TextViews in expanded layouts
val layoutType = FLAG_CONTENT_VIEW_EXPANDED
inflaterFactory =
- NotifLayoutInflaterFactory(
+ createNotifLayoutInflaterFactory(
row,
layoutType,
setOf(
@@ -147,4 +147,18 @@ class NotifLayoutInflaterFactoryTest : SysuiTestCase() {
null
}
}
+
+ private fun createNotifLayoutInflaterFactory(
+ row: ExpandableNotificationRow,
+ layoutType: Int,
+ notifRemoteViewsFactoryContainer: Set<NotifRemoteViewsFactory>
+ ) =
+ NotifLayoutInflaterFactory(
+ row,
+ layoutType,
+ object : NotifRemoteViewsFactoryContainer {
+ override val factories: Set<NotifRemoteViewsFactory> =
+ notifRemoteViewsFactoryContainer
+ }
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index b0996ad48d0a..a0d10759ba56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -88,6 +88,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
private Notification.Builder mBuilder;
private ExpandableNotificationRow mRow;
+ private NotificationTestHelper mHelper;
+
@Mock private NotifRemoteViewCache mCache;
@Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
@Mock private InflatedSmartReplyState mInflatedSmartReplyState;
@@ -119,11 +121,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
.setContentTitle("Title")
.setContentText("Text")
.setStyle(new Notification.BigTextStyle().bigText("big text"));
- NotificationTestHelper helper = new NotificationTestHelper(
+ mHelper = new NotificationTestHelper(
mContext,
mDependency,
TestableLooper.get(this));
- ExpandableNotificationRow row = helper.createRow(mBuilder.build());
+ ExpandableNotificationRow row = mHelper.createRow(mBuilder.build());
mRow = spy(row);
when(mNotifLayoutInflaterFactoryProvider.provide(any(), any()))
.thenReturn(mNotifLayoutInflaterFactory);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 71613edb8737..65491937c285 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -69,9 +69,9 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -124,7 +124,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
- private TestScope mTestScope = TestScopeProvider.getTestScope();
+ private KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private TestScope mTestScope = mKosmos.getTestScope();
private JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private TestableLooper mTestableLooper;
@@ -182,7 +183,10 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
new FakeKeyguardRepository(),
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
- mActiveNotificationsInteractor);
+ mActiveNotificationsInteractor,
+ mKosmos.getFakeSceneContainerFlags(),
+ () -> mKosmos.getSceneInteractor()
+ );
mGutsManager = new NotificationGutsManager(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
new file mode 100644
index 000000000000..446b9d0bd434
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.R
+import android.app.AppOpsManager
+import android.app.INotificationManager
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutManager
+import android.content.pm.launcherApps
+import android.graphics.Color
+import android.os.Binder
+import android.os.Handler
+import android.os.userManager
+import android.provider.Settings
+import android.service.notification.NotificationListenerService.Ranking
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.util.ArraySet
+import android.view.View
+import android.view.accessibility.accessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.metricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractorFactory.create
+import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
+import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.shade.shadeControllerSceneImpl
+import com.android.systemui.statusbar.NotificationEntryHelper
+import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AssistantFeedbackController
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.notificationLockscreenUserManager
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.wmshell.BubblesManager
+import java.util.Optional
+import junit.framework.Assert
+import kotlin.test.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runCurrent
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.invocation.InvocationOnMock
+
+/** Tests for [NotificationGutsManager] with the scene container enabled. */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotificationGutsManagerWithScenesTest : SysuiTestCase() {
+ private val testNotificationChannel =
+ NotificationChannel(
+ TEST_CHANNEL_ID,
+ TEST_CHANNEL_ID,
+ NotificationManager.IMPORTANCE_DEFAULT
+ )
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val javaAdapter = JavaAdapter(testScope.backgroundScope)
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var handler: Handler
+ private lateinit var helper: NotificationTestHelper
+ private lateinit var gutsManager: NotificationGutsManager
+ private lateinit var windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
+
+ private val metricsLogger = kosmos.metricsLogger
+ private val deviceProvisionedController = kosmos.deviceProvisionedController
+ private val accessibilityManager = kosmos.accessibilityManager
+ private val mBarService = kosmos.statusBarService
+ private val launcherApps = kosmos.launcherApps
+ private val shadeController = kosmos.shadeControllerSceneImpl
+ private val notificationLockscreenUserManager = kosmos.notificationLockscreenUserManager
+ private val statusBarStateController = kosmos.statusBarStateController
+ private val headsUpManager = kosmos.headsUpManager
+ private val activityStarter = kosmos.activityStarter
+ private val userManager = kosmos.userManager
+ private val activeNotificationsInteractor = kosmos.activeNotificationsInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+
+ @Mock private lateinit var onUserInteractionCallback: OnUserInteractionCallback
+ @Mock private lateinit var presenter: NotificationPresenter
+ @Mock private lateinit var notificationActivityStarter: NotificationActivityStarter
+ @Mock private lateinit var notificationListContainer: NotificationListContainer
+ @Mock
+ private lateinit var onSettingsClickListener: NotificationGutsManager.OnSettingsClickListener
+ @Mock private lateinit var highPriorityProvider: HighPriorityProvider
+ @Mock private lateinit var notificationManager: INotificationManager
+ @Mock private lateinit var shortcutManager: ShortcutManager
+ @Mock private lateinit var channelEditorDialogController: ChannelEditorDialogController
+ @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier
+ @Mock private lateinit var contextTracker: UserContextProvider
+ @Mock private lateinit var bubblesManager: BubblesManager
+ @Mock private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
+ @Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+ sceneContainerFlags.enabled = true
+ testableLooper = TestableLooper.get(this)
+ allowTestableLooperAsMainThread()
+ handler = Handler.createAsync(testableLooper.getLooper())
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ Mockito.`when`(accessibilityManager.isTouchExplorationEnabled).thenReturn(false)
+ windowRootViewVisibilityInteractor =
+ WindowRootViewVisibilityInteractor(
+ testScope.backgroundScope,
+ WindowRootViewVisibilityRepository(mBarService, executor),
+ FakeKeyguardRepository(),
+ headsUpManager,
+ create().powerInteractor,
+ activeNotificationsInteractor,
+ sceneContainerFlags,
+ { sceneInteractor },
+ )
+ gutsManager =
+ NotificationGutsManager(
+ mContext,
+ handler,
+ handler,
+ javaAdapter,
+ accessibilityManager,
+ highPriorityProvider,
+ notificationManager,
+ userManager,
+ peopleSpaceWidgetManager,
+ launcherApps,
+ shortcutManager,
+ channelEditorDialogController,
+ contextTracker,
+ assistantFeedbackController,
+ Optional.of(bubblesManager),
+ UiEventLoggerFake(),
+ onUserInteractionCallback,
+ shadeController,
+ windowRootViewVisibilityInteractor,
+ notificationLockscreenUserManager,
+ statusBarStateController,
+ mBarService,
+ deviceProvisionedController,
+ metricsLogger,
+ headsUpManager,
+ activityStarter
+ )
+ gutsManager.setUpWithPresenter(
+ presenter,
+ notificationListContainer,
+ onSettingsClickListener
+ )
+ gutsManager.setNotificationActivityStarter(notificationActivityStarter)
+ gutsManager.start()
+ }
+
+ @Test
+ fun testOpenAndCloseGuts() {
+ val guts = Mockito.spy(NotificationGuts(mContext))
+ Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+ ->
+ handler.post((invocation.arguments[0] as Runnable))
+ null
+ }
+
+ // Test doesn't support animation since the guts view is not attached.
+ Mockito.doNothing()
+ .`when`(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ val realRow = createTestNotificationRow()
+ val menuItem = createTestMenuItem(realRow)
+ val row = Mockito.spy(realRow)
+ Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+ Mockito.`when`(row.guts).thenReturn(guts)
+ Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+ assertEquals(View.INVISIBLE.toLong(), guts.visibility.toLong())
+ testableLooper.processAllMessages()
+ verify(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ verify(headsUpManager).setGutsShown(realRow!!.entry, true)
+ assertEquals(View.VISIBLE.toLong(), guts.visibility.toLong())
+ gutsManager.closeAndSaveGuts(false, false, true, 0, 0, false)
+ verify(guts)
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+ verify(row, Mockito.times(1)).setGutsView(ArgumentMatchers.any())
+ testableLooper.processAllMessages()
+ verify(headsUpManager).setGutsShown(realRow.entry, false)
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_visible_gutsNotClosed() {
+ // First, start out lockscreen or shade as not visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+ val guts = Mockito.mock(NotificationGuts::class.java)
+ gutsManager.exposedGuts = guts
+
+ // WHEN the lockscreen or shade becomes visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the guts are not closed
+ verify(guts, Mockito.never()).removeCallbacks(ArgumentMatchers.any())
+ verify(guts, Mockito.never())
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_notVisible_gutsClosed() {
+ // First, start out lockscreen or shade as visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+ val guts = Mockito.mock(NotificationGuts::class.java)
+ gutsManager.exposedGuts = guts
+
+ // WHEN the lockscreen or shade is no longer visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the guts are closed
+ verify(guts).removeCallbacks(ArgumentMatchers.any())
+ verify(guts)
+ .closeControls(
+ /* leavebehinds= */ ArgumentMatchers.eq(true),
+ /* controls= */ ArgumentMatchers.eq(true),
+ /* x= */ ArgumentMatchers.anyInt(),
+ /* y= */ ArgumentMatchers.anyInt(),
+ /* force= */ ArgumentMatchers.eq(true)
+ )
+ }
+
+ @Test
+ fun testLockscreenShadeVisible_notVisible_listContainerReset() {
+ // First, start out lockscreen or shade as visible
+ setIsLockscreenOrShadeVisible(true)
+ testScope.testScheduler.runCurrent()
+
+ // WHEN the lockscreen or shade is no longer visible
+ setIsLockscreenOrShadeVisible(false)
+ testScope.testScheduler.runCurrent()
+
+ // THEN the list container is reset
+ verify(notificationListContainer)
+ .resetExposedMenuView(ArgumentMatchers.anyBoolean(), ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ fun testChangeDensityOrFontScale() {
+ val guts = Mockito.spy(NotificationGuts(mContext))
+ Mockito.`when`(guts.post(ArgumentMatchers.any())).thenAnswer { invocation: InvocationOnMock
+ ->
+ handler.post((invocation.arguments[0] as Runnable))
+ null
+ }
+
+ // Test doesn't support animation since the guts view is not attached.
+ Mockito.doNothing()
+ .`when`(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+ val realRow = createTestNotificationRow()
+ val menuItem = createTestMenuItem(realRow)
+ val row = Mockito.spy(realRow)
+ Mockito.`when`(row!!.windowToken).thenReturn(Binder())
+ Mockito.`when`(row.guts).thenReturn(guts)
+ Mockito.doNothing().`when`(row).ensureGutsInflated()
+ val realEntry = realRow!!.entry
+ val entry = Mockito.spy(realEntry)
+ Mockito.`when`(entry.row).thenReturn(row)
+ Mockito.`when`(entry.getGuts()).thenReturn(guts)
+ Assert.assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem))
+ testableLooper.processAllMessages()
+ verify(guts)
+ .openControls(
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.any(Runnable::class.java)
+ )
+
+ // called once by mGutsManager.bindGuts() in mGutsManager.openGuts()
+ verify(row).setGutsView(ArgumentMatchers.any())
+ row.onDensityOrFontScaleChanged()
+ gutsManager.onDensityOrFontScaleChanged(entry)
+ testableLooper.processAllMessages()
+ gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false)
+ verify(guts)
+ .closeControls(
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyBoolean(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.anyBoolean()
+ )
+
+ // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged()
+ verify(row, Mockito.times(2)).setGutsView(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_mic() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_mic() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Intent.ACTION_MANAGE_APP_PERMISSIONS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_MANAGE_APP_OVERLAY_PERMISSION, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_mic_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_camera_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_CAMERA)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ fun testAppOpsSettingsIntent_mic_overlay() {
+ val ops = ArraySet<Int>()
+ ops.add(AppOpsManager.OP_RECORD_AUDIO)
+ ops.add(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+ gutsManager.startAppOpsSettingsActivity("", 0, ops, null)
+ val captor = ArgumentCaptor.forClass(Intent::class.java)
+ verify(notificationActivityStarter, Mockito.times(1))
+ .startNotificationGutsIntent(
+ captor.capture(),
+ ArgumentMatchers.anyInt(),
+ ArgumentMatchers.any()
+ )
+ assertEquals(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, captor.value.action)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_highPriority() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ val entry = row.entry
+ NotificationEntryHelper.modifyRanking(entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .setImportance(NotificationManager.IMPORTANCE_HIGH)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ Mockito.`when`(highPriorityProvider.isHighPriority(entry)).thenReturn(true)
+ val statusBarNotification = entry.sbn
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(true), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_PassesAlongProvisionedState() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ val statusBarNotification = row.entry.sbn
+ val entry = row.entry
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(false), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testInitializeNotificationInfoView_withInitialAction() {
+ val notificationInfoView = Mockito.mock(NotificationInfo::class.java)
+ val row = Mockito.spy(helper.createRow())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setUserSentiment(Ranking.USER_SENTIMENT_NEGATIVE)
+ .build()
+ Mockito.`when`(row.getIsNonblockable()).thenReturn(false)
+ val statusBarNotification = row.entry.sbn
+ val entry = row.entry
+ gutsManager.initializeNotificationInfo(row, notificationInfoView)
+ verify(notificationInfoView)
+ .bindNotification(
+ ArgumentMatchers.any(PackageManager::class.java),
+ ArgumentMatchers.any(INotificationManager::class.java),
+ ArgumentMatchers.eq(onUserInteractionCallback),
+ ArgumentMatchers.eq(channelEditorDialogController),
+ ArgumentMatchers.eq(statusBarNotification.packageName),
+ ArgumentMatchers.any(NotificationChannel::class.java),
+ ArgumentMatchers.eq(entry),
+ ArgumentMatchers.any(NotificationInfo.OnSettingsClickListener::class.java),
+ ArgumentMatchers.any(NotificationInfo.OnAppSettingsClickListener::class.java),
+ ArgumentMatchers.any(UiEventLogger::class.java),
+ ArgumentMatchers.eq(true),
+ ArgumentMatchers.eq(false),
+ ArgumentMatchers.eq(false), /* wasShownHighPriority */
+ ArgumentMatchers.eq(assistantFeedbackController),
+ ArgumentMatchers.any(MetricsLogger::class.java)
+ )
+ }
+
+ private fun createTestNotificationRow(): ExpandableNotificationRow? {
+ val nb =
+ Notification.Builder(mContext, testNotificationChannel.id)
+ .setContentTitle("foo")
+ .setColorized(true)
+ .setColor(Color.RED)
+ .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+ .setSmallIcon(R.drawable.sym_def_app_icon)
+ return try {
+ val row = helper.createRow(nb.build())
+ NotificationEntryHelper.modifyRanking(row.entry)
+ .setChannel(testNotificationChannel)
+ .build()
+ row
+ } catch (e: Exception) {
+ org.junit.Assert.fail()
+ null
+ }
+ }
+
+ private fun setIsLockscreenOrShadeVisible(isVisible: Boolean) {
+ val key =
+ if (isVisible) {
+ SceneKey.Lockscreen
+ } else {
+ SceneKey.Bouncer
+ }
+ sceneInteractor.changeScene(SceneModel(key), "test")
+ sceneInteractor.setTransitionState(
+ MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+ )
+ testScope.runCurrent()
+ }
+
+ private fun createTestMenuItem(
+ row: ExpandableNotificationRow?
+ ): NotificationMenuRowPlugin.MenuItem {
+ val menuRow: NotificationMenuRowPlugin =
+ NotificationMenuRow(mContext, peopleNotificationIdentifier)
+ menuRow.createMenu(row, row!!.entry.sbn)
+ val menuItem = menuRow.getLongpressMenuItem(mContext)
+ Assert.assertNotNull(menuItem)
+ return menuItem
+ }
+
+ companion object {
+ private const val TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
new file mode 100644
index 000000000000..1c959af6ec3f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.row
+
+import android.app.Notification
+import android.app.Person
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder
+import com.android.systemui.util.mockito.mock
+import kotlin.test.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineConversationViewBinderTest : SysuiTestCase() {
+ private lateinit var notificationBuilder: Notification.Builder
+ private lateinit var helper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ helper = NotificationTestHelper(context, mDependency, TestableLooper.get(this))
+ notificationBuilder = Notification.Builder(context, CHANNEL_ID)
+ notificationBuilder
+ .setSmallIcon(R.drawable.ic_corp_icon)
+ .setContentTitle(CONTENT_TITLE)
+ .setContentText(CONTENT_TEXT)
+ }
+
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun bindGroupConversationSingleLineView() {
+ // GIVEN a row with a group conversation notification
+ val user =
+ Person.Builder()
+ // .setIcon(Icon.createWithResource(mContext,
+ // R.drawable.ic_account_circle))
+ .setName(USER_NAME)
+ .build()
+ val style =
+ Notification.MessagingStyle(user)
+ .addMessage(MESSAGE_TEXT, System.currentTimeMillis(), user)
+ .addMessage(
+ "How about lunch?",
+ System.currentTimeMillis(),
+ Person.Builder().setName("user2").build()
+ )
+ .setGroupConversation(true)
+ notificationBuilder.setStyle(style).setShortcutId(SHORTCUT_ID)
+ val notification = notificationBuilder.build()
+ val row = helper.createRow(notification)
+
+ val viewHolder =
+ inflateSingleLineViewHolder(
+ isConversation = true,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock()
+ )
+ as HybridConversationNotificationView
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = style,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ )
+ // WHEN: binds the viewHolder
+ SingleLineConversationViewBinder.bind(
+ viewModel,
+ viewHolder,
+ )
+
+ // THEN: the single-line conversation view should be bind with view model's corresponding
+ // fields
+ assertEquals(viewModel.titleText, viewHolder.titleView.text)
+ assertEquals(viewModel.contentText, viewHolder.textView.text)
+ assertEquals(
+ viewModel.conversationData?.conversationSenderName,
+ viewHolder.conversationSenderNameView.text
+ )
+ }
+
+ private companion object {
+ const val CHANNEL_ID = "CHANNEL_ID"
+ const val CONTENT_TITLE = "CONTENT_TITLE"
+ const val CONTENT_TEXT = "CONTENT_TEXT"
+ const val USER_NAME = "USER_NAME"
+ const val MESSAGE_TEXT = "MESSAGE_TEXT"
+ const val SHORTCUT_ID = "Shortcut"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
new file mode 100644
index 000000000000..f0fc349777b2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.row
+
+import android.app.Notification
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.SingleLineViewInflater.inflateSingleLineViewHolder
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SingleLineViewBinderTest : SysuiTestCase() {
+ private lateinit var notificationBuilder: Notification.Builder
+ private lateinit var helper: NotificationTestHelper
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ notificationBuilder = Notification.Builder(mContext, CHANNEL_ID)
+ notificationBuilder
+ .setSmallIcon(R.drawable.ic_corp_icon)
+ .setContentTitle(CONTENT_TITLE)
+ .setContentText(CONTENT_TEXT)
+ }
+
+ @Test
+ @EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+ fun bindNonConversationSingleLineView() {
+ // GIVEN: a row with bigText style notification
+ val style = Notification.BigTextStyle().bigText(CONTENT_TEXT)
+ notificationBuilder.setStyle(style)
+ val notification = notificationBuilder.build()
+ val row: ExpandableNotificationRow = helper.createRow(notification)
+
+ val viewHolder =
+ inflateSingleLineViewHolder(
+ isConversation = false,
+ reinflateFlags = FLAG_CONTENT_VIEW_SINGLE_LINE,
+ entry = row.entry,
+ context = context,
+ logger = mock()
+ )
+ val viewModel =
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = notification,
+ messagingStyle = null,
+ builder = notificationBuilder,
+ systemUiContext = context,
+ )
+
+ // WHEN: binds the viewHolder
+ SingleLineViewBinder.bind(viewModel, viewHolder)
+
+ // THEN: the single-line view should be bind with viewModel's title and content text
+ Assert.assertEquals(viewModel.titleText, viewHolder?.titleView?.text)
+ Assert.assertEquals(viewModel.contentText, viewHolder?.textView?.text)
+ }
+
+ private companion object {
+ const val CHANNEL_ID = "CHANNEL_ID"
+ const val CONTENT_TITLE = "A Cool New Feature"
+ const val CONTENT_TEXT = "Checkout out new feature!"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
new file mode 100644
index 000000000000..b67153a842ac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -0,0 +1,463 @@
+/*
+ * 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.row
+
+import android.app.Notification
+import android.app.Person
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.ConversationAvatar
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.FacePile
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleIcon
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertIsNot
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@EnableFlags(AsyncHybridViewInflation.FLAG_NAME)
+class SingleLineViewInflaterTest : SysuiTestCase() {
+ private lateinit var helper: NotificationTestHelper
+ // Non-group MessagingStyles only have firstSender
+ private lateinit var firstSender: Person
+ private lateinit var lastSender: Person
+ private lateinit var firstSenderIcon: Icon
+ private lateinit var lastSenderIcon: Icon
+ private var firstSenderIconDrawable: Drawable? = null
+ private var lastSenderIconDrawable: Drawable? = null
+ private val currentUser: Person? = null
+
+ private companion object {
+ const val FIRST_SENDER_NAME = "First Sender"
+ const val LAST_SENDER_NAME = "Second Sender"
+ const val LAST_MESSAGE = "How about lunch?"
+
+ const val CONVERSATION_TITLE = "The Sender Family"
+ const val CONTENT_TITLE = "A Cool Group"
+ const val CONTENT_TEXT = "This is an amazing group chat"
+
+ const val SHORTCUT_ID = "Shortcut"
+ }
+
+ @Before
+ fun setUp() {
+ helper = NotificationTestHelper(mContext, mDependency, TestableLooper.get(this))
+ firstSenderIcon = Icon.createWithBitmap(getBitmap(context, R.drawable.ic_person))
+ firstSenderIconDrawable = firstSenderIcon.loadDrawable(context)
+ lastSenderIcon =
+ Icon.createWithBitmap(
+ getBitmap(context, com.android.internal.R.drawable.ic_account_circle)
+ )
+ lastSenderIconDrawable = lastSenderIcon.loadDrawable(context)
+ firstSender = Person.Builder().setName(FIRST_SENDER_NAME).setIcon(firstSenderIcon).build()
+ lastSender = Person.Builder().setName(LAST_SENDER_NAME).setIcon(lastSenderIcon).build()
+ }
+
+ @Test
+ fun createViewModelForNonConversationSingleLineView() {
+ // Given: a non-conversation notification
+ val notificationType = NonMessaging()
+ val notification = getNotification(NonMessaging())
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // conversationData: null, because it's not a conversation notification
+ assertEquals(SingleLineViewModel(CONTENT_TITLE, CONTENT_TEXT, null), singleLineViewModel)
+ }
+
+ @Test
+ fun createViewModelForNonGroupConversationNotification() {
+ // Given: a non-group conversation notification
+ val notificationType = OneToOneConversation()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: null, because it's not a group conversation
+ // conversationData.avatar: a single icon of the last sender
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData?.conversationSenderName,
+ "Sender name should be null for one-on-one conversation"
+ )
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(SingleIcon(firstSenderIcon.loadDrawable(context))) == true
+ }
+ }
+
+ @Test
+ fun createViewModelForNonGroupLegacyMessagingStyleNotification() {
+ // Given: a non-group legacy messaging style notification
+ val notificationType = LegacyMessaging()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // titleText: CONVERSATION_TITLE: SENDER_NAME
+ // contentText: the last message text
+ // conversationData: null, because it's not a conversation notification
+ assertEquals("$CONVERSATION_TITLE: $FIRST_SENDER_NAME", singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData,
+ "conversationData should be null for legacy messaging conversation"
+ )
+ }
+
+ @Test
+ fun createViewModelForGroupLegacyMessagingStyleNotification() {
+ // Given: a non-group legacy messaging style notification
+ val notificationType = LegacyMessagingGroup()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be as expected
+ // titleText: CONVERSATION_TITLE: LAST_SENDER_NAME
+ // contentText: the last message text
+ // conversationData: null, because it's not a conversation notification
+ assertEquals("$CONVERSATION_TITLE: $LAST_SENDER_NAME", singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData,
+ "conversationData should be null for legacy messaging conversation"
+ )
+ }
+
+ @Test
+ fun createViewModelForNonGroupConversationNotificationWithShortcutIcon() {
+ // Given: a non-group conversation notification with a shortcut icon
+ val shortcutIcon =
+ Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+ val notificationType = OneToOneConversation(shortcutIcon = shortcutIcon)
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: null, because it's not a group conversation
+ // conversationData.avatar: a single icon of the shortcut icon
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertNull(
+ singleLineViewModel.conversationData?.conversationSenderName,
+ "Sender name should be null for one-on-one conversation"
+ )
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(SingleIcon(shortcutIcon.loadDrawable(context))) == true
+ }
+ }
+
+ @Test
+ fun createViewModelForGroupConversationNotificationWithLargeIcon() {
+ // Given: a group conversation notification with a large icon
+ val largeIcon =
+ Icon.createWithResource(context, com.android.internal.R.drawable.ic_account_circle)
+ val notificationType = GroupConversation(largeIcon = largeIcon)
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: the last non-user sender's name
+ // conversationData.avatar: a single icon
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertEquals(
+ context.resources.getString(
+ com.android.internal.R.string.conversation_single_line_name_display,
+ LAST_SENDER_NAME
+ ),
+ singleLineViewModel.conversationData?.conversationSenderName
+ )
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(SingleIcon(largeIcon.loadDrawable(context))) == true
+ }
+ }
+
+ @Test
+ fun createViewModelForGroupConversationWithNoIcon() {
+ // Given: a group conversation notification
+ val notificationType = GroupConversation()
+ val notification = getNotification(notificationType)
+
+ // When: inflate the SingleLineViewModel
+ val singleLineViewModel = notification.makeSingleLineViewModel(notificationType)
+
+ // Then: the inflated SingleLineViewModel should be expected
+ // titleText: Notification.ConversationTitle
+ // contentText: the last message text
+ // conversationSenderName: the last non-user sender's name
+ // conversationData.avatar: a face-pile consists the last sender's icon
+ assertEquals(CONVERSATION_TITLE, singleLineViewModel.titleText)
+ assertEquals(LAST_MESSAGE, singleLineViewModel.contentText)
+ assertEquals(
+ context.resources.getString(
+ com.android.internal.R.string.conversation_single_line_name_display,
+ LAST_SENDER_NAME
+ ),
+ singleLineViewModel.conversationData?.conversationSenderName
+ )
+
+ val backgroundColor =
+ Notification.Builder.recoverBuilder(context, notification)
+ .getBackgroundColor(/* isHeader = */ false)
+ assertTrue {
+ singleLineViewModel.conversationData
+ ?.avatar
+ ?.equalsTo(
+ FacePile(
+ firstSenderIconDrawable,
+ lastSenderIconDrawable,
+ backgroundColor,
+ )
+ ) == true
+ }
+ }
+
+ sealed class NotificationType(val largeIcon: Icon? = null)
+
+ class NonMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ class LegacyMessaging(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ class LegacyMessagingGroup(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ class OneToOneConversation(largeIcon: Icon? = null, val shortcutIcon: Icon? = null) :
+ NotificationType(largeIcon)
+
+ class GroupConversation(largeIcon: Icon? = null) : NotificationType(largeIcon)
+
+ private fun getNotification(type: NotificationType): Notification {
+ val notificationBuilder: Notification.Builder =
+ Notification.Builder(mContext, "channelId")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle(CONTENT_TITLE)
+ .setContentText(CONTENT_TEXT)
+ .setLargeIcon(type.largeIcon)
+
+ val user = Person.Builder().setName("User").build()
+
+ val buildMessagingStyle =
+ Notification.MessagingStyle(user)
+ .setConversationTitle(CONVERSATION_TITLE)
+ .addMessage("Hi", 0, currentUser)
+
+ return when (type) {
+ is NonMessaging ->
+ notificationBuilder
+ .setStyle(Notification.BigTextStyle().bigText("Big Text"))
+ .build()
+ is LegacyMessaging -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Not much", 0, currentUser)
+ .addMessage(LAST_MESSAGE, 0, firstSender)
+
+ val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+ assertNull(notification.shortcutId)
+ notification
+ }
+ is LegacyMessagingGroup -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Check out my new hover board!", 0, lastSender)
+ .setGroupConversation(true)
+ .addMessage(LAST_MESSAGE, 0, lastSender)
+
+ val notification = notificationBuilder.setStyle(buildMessagingStyle).build()
+
+ assertNull(notification.shortcutId)
+ notification
+ }
+ is OneToOneConversation -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Not much", 0, currentUser)
+ .addMessage(LAST_MESSAGE, 0, firstSender)
+ .setShortcutIcon(type.shortcutIcon)
+ notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+ }
+ is GroupConversation -> {
+ buildMessagingStyle
+ .addMessage("What's up?", 0, firstSender)
+ .addMessage("Check out my new hover board!", 0, lastSender)
+ .setGroupConversation(true)
+ .addMessage(LAST_MESSAGE, 0, lastSender)
+ notificationBuilder.setShortcutId(SHORTCUT_ID).setStyle(buildMessagingStyle).build()
+ }
+ }
+ }
+
+ private fun Notification.makeSingleLineViewModel(type: NotificationType): SingleLineViewModel {
+ val builder = Notification.Builder.recoverBuilder(context, this)
+
+ // Validate the recovered builder has the right type of style
+ val expectMessagingStyle =
+ when (type) {
+ is LegacyMessaging,
+ is LegacyMessagingGroup,
+ is OneToOneConversation,
+ is GroupConversation -> true
+ else -> false
+ }
+ if (expectMessagingStyle) {
+ assertIs<Notification.MessagingStyle>(
+ builder.style,
+ "Notification style should be MessagingStyle"
+ )
+ } else {
+ assertIsNot<Notification.MessagingStyle>(
+ builder.style,
+ message = "Notification style should not be MessagingStyle"
+ )
+ }
+
+ // Inflate the SingleLineViewModel
+ // Mock the behavior of NotificationContentInflater.doInBackground
+ val messagingStyle = builder.getMessagingStyle()
+ val isConversation = type is OneToOneConversation || type is GroupConversation
+ return SingleLineViewInflater.inflateSingleLineViewModel(
+ this,
+ if (isConversation) messagingStyle else null,
+ builder,
+ context
+ )
+ }
+
+ private fun Notification.Builder.getMessagingStyle(): Notification.MessagingStyle? {
+ return style as? Notification.MessagingStyle
+ }
+
+ private fun getBitmap(context: Context, resId: Int): Bitmap {
+ val largeIconDimension =
+ context.resources.getDimension(R.dimen.conversation_single_line_avatar_size)
+ val d = context.resources.getDrawable(resId)
+ val b =
+ Bitmap.createBitmap(
+ largeIconDimension.toInt(),
+ largeIconDimension.toInt(),
+ Bitmap.Config.ARGB_8888
+ )
+ val c = Canvas(b)
+ val paint = Paint()
+ c.drawCircle(
+ largeIconDimension / 2,
+ largeIconDimension / 2,
+ largeIconDimension.coerceAtMost(largeIconDimension) / 2,
+ paint
+ )
+ d.setBounds(0, 0, largeIconDimension.toInt(), largeIconDimension.toInt())
+ paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
+ c.saveLayer(0F, 0F, largeIconDimension, largeIconDimension, paint, Canvas.ALL_SAVE_FLAG)
+ d.draw(c)
+ c.restore()
+ return b
+ }
+
+ fun ConversationAvatar.equalsTo(other: ConversationAvatar?): Boolean =
+ when {
+ this === other -> true
+ this is SingleIcon && other is SingleIcon -> equalsTo(other)
+ this is FacePile && other is FacePile -> equalsTo(other)
+ else -> false
+ }
+
+ private fun SingleIcon.equalsTo(other: SingleIcon): Boolean =
+ iconDrawable?.equalsTo(other.iconDrawable) == true
+
+ private fun FacePile.equalsTo(other: FacePile): Boolean =
+ when {
+ bottomBackgroundColor != other.bottomBackgroundColor -> false
+ topIconDrawable?.equalsTo(other.topIconDrawable) != true -> false
+ bottomIconDrawable?.equalsTo(other.bottomIconDrawable) != true -> false
+ else -> true
+ }
+
+ fun Drawable.equalsTo(other: Drawable?): Boolean =
+ when {
+ this === other -> true
+ this.pixelsEqualTo(other) -> true
+ else -> false
+ }
+
+ private fun <T : Drawable> T.pixelsEqualTo(t: T?) =
+ toBitmap().pixelsEqualTo(t?.toBitmap(), false)
+
+ private fun Bitmap.pixelsEqualTo(otherBitmap: Bitmap?, shouldRecycle: Boolean = false) =
+ otherBitmap?.let { other ->
+ if (width == other.width && height == other.height) {
+ val res = toPixels().contentEquals(other.toPixels())
+ if (shouldRecycle) {
+ doRecycle().also { otherBitmap.doRecycle() }
+ }
+ res
+ } else false
+ }
+ ?: kotlin.run { false }
+
+ private fun Bitmap.toPixels() =
+ IntArray(width * height).apply { getPixels(this, 0, width, 0, 0, width, height) }
+
+ fun Bitmap.doRecycle() {
+ if (!isRecycled) recycle()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 1ab4c32c7d08..dbe63f290407 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -860,9 +860,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should not be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
@@ -884,9 +881,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
/* hasClearableSilentNotifs = */ false)
);
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -908,9 +902,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
/* hasClearableSilentNotifs = */ false)
);
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -925,9 +916,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
- // WHEN: call updateImportantForAccessibility
- mController.updateImportantForAccessibility();
-
// THEN: mNotificationStackScrollLayout should be important for A11y
verify(mNotificationStackScrollLayout)
.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 06298b78ae57..32c727c70172 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -38,6 +38,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
@@ -45,6 +48,7 @@ import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
+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
@@ -55,15 +59,22 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
class SharedNotificationContainerViewModelTest : SysuiTestCase() {
+ val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
+ lateinit var translationYFlow: MutableStateFlow<Float>
val kosmos =
testKosmos().apply {
fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }
}
+
+ init {
+ kosmos.aodBurnInViewModel = aodBurnInViewModel
+ }
val testScope = kosmos.testScope
val configurationRepository = kosmos.fakeConfigurationRepository
val keyguardRepository = kosmos.fakeKeyguardRepository
@@ -75,11 +86,14 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
val sharedNotificationContainerInteractor = kosmos.sharedNotificationContainerInteractor
val largeScreenHeaderHelper = kosmos.mockLargeScreenHeaderHelper
- val underTest = kosmos.sharedNotificationContainerViewModel
+ lateinit var underTest: SharedNotificationContainerViewModel
@Before
fun setUp() {
overrideResource(R.bool.config_use_split_notification_shade, false)
+ translationYFlow = MutableStateFlow(0f)
+ whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+ underTest = kosmos.sharedNotificationContainerViewModel
}
@Test
@@ -579,9 +593,21 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ fun translationYUpdatesOnKeyguardForBurnIn() =
+ testScope.runTest {
+ val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
+
+ showLockscreen()
+ assertThat(translationY).isEqualTo(0)
+
+ translationYFlow.value = 150f
+ assertThat(translationY).isEqualTo(150f)
+ }
+
+ @Test
fun translationYUpdatesOnKeyguard() =
testScope.runTest {
- val translationY by collectLastValue(underTest.translationY)
+ val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
configurationRepository.setDimensionPixelSize(
R.dimen.keyguard_translate_distance_on_swipe_up,
@@ -601,7 +627,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
@Test
fun translationYDoesNotUpdateWhenShadeIsExpanded() =
testScope.runTest {
- val translationY by collectLastValue(underTest.translationY)
+ val translationY by collectLastValue(underTest.translationY(BurnInParameters()))
configurationRepository.setDimensionPixelSize(
R.dimen.keyguard_translate_distance_on_swipe_up,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 8dde9359bdfc..cb4531567e86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -182,8 +182,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
- mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
- mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ mSetFlagsRule.disableFlags(
+ com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR,
+ com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+ );
when(mNotificationShadeWindowController.getWindowRootView())
.thenReturn(mNotificationShadeWindowView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 0e0d4897d667..5b5819d649b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -125,22 +125,6 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun satelliteManagerThrows_doesNotCrash() =
- testScope.runTest {
- setupDefaultRepo()
-
- whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any()))
- .thenThrow(SatelliteException(13))
-
- val conn by collectLastValue(underTest.connectionState)
- val strength by collectLastValue(underTest.signalStrength)
-
- // Flows have not emitted, we haven't crashed
- assertThat(conn).isNull()
- assertThat(strength).isNull()
- }
-
- @Test
fun connectionState_mapsFromSatelliteModemState() =
testScope.runTest {
setupDefaultRepo()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index b58a41c89a4e..457acd214222 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -190,7 +190,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
mWakefulnessLifecycle.dispatchFinishedWakingUp();
mThemeOverlayController.start();
- verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mMainExecutor));
+ verify(mUserTracker).addCallback(mUserTrackerCallback.capture(), eq(mBgExecutor));
verify(mWallpaperManager).addOnColorsChangedListener(mColorsListener.capture(), eq(null),
eq(UserHandle.USER_ALL));
verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiver.capture(), any(),
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
new file mode 100644
index 000000000000..2a0559869c86
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.windowManager by Kosmos.Fixture<WindowManager> { mock(WindowManager::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
new file mode 100644
index 000000000000..e547da1b92dd
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.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.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAccessibilityQsShortcutsRepository : AccessibilityQsShortcutsRepository {
+
+ private val targetsPerUser = mutableMapOf<Int, MutableSharedFlow<Set<String>>>()
+
+ override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+ return getFlow(userId).asSharedFlow()
+ }
+
+ /**
+ * Set the a11y qs shortcut targets. In real world, the A11y QS Shortcut targets are set by the
+ * Settings app not in SysUi
+ */
+ suspend fun setA11yQsShortcutTargets(userId: Int, targets: Set<String>) {
+ getFlow(userId).emit(targets)
+ }
+
+ private fun getFlow(userId: Int): MutableSharedFlow<Set<String>> =
+ targetsPerUser.getOrPut(userId) { MutableSharedFlow(replay = 1) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt
new file mode 100644
index 000000000000..8fcb60cec838
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FakeFingerprintInteractiveToAuthProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.hardware.biometrics.common.AuthenticateReason
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeFingerprintInteractiveToAuthProvider : FingerprintInteractiveToAuthProvider {
+ override val enabledForCurrentUser: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ private val userIdToExtension = mutableMapOf<Int, AuthenticateReason.Vendor>()
+ override fun getVendorExtension(userId: Int): AuthenticateReason.Vendor? =
+ userIdToExtension[userId]
+
+ fun setVendorExtension(userId: Int, extension: AuthenticateReason.Vendor) {
+ userIdToExtension[userId] = extension
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt
new file mode 100644
index 000000000000..57dc37e3defa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProviderKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fingerprintInteractiveToAuthProvider by
+ Kosmos.Fixture { fakeFingerprintInteractiveToAuthProvider }
+
+val Kosmos.fakeFingerprintInteractiveToAuthProvider by
+ Kosmos.Fixture { FakeFingerprintInteractiveToAuthProvider() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
index 8702e00b1e97..b5515c49cdbe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepositoryKosmos.kt
@@ -19,4 +19,6 @@ package com.android.systemui.biometrics.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.fingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
+val Kosmos.fingerprintPropertyRepository by Fixture { fakeFingerprintPropertyRepository }
+
+val Kosmos.fakeFingerprintPropertyRepository by Fixture { FakeFingerprintPropertyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
new file mode 100644
index 000000000000..979a49b7e70f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.content.applicationContext
+import android.view.windowManager
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.biometrics.fingerprintInteractiveToAuthProvider
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.SideFpsLogger
+import java.util.Optional
+import org.mockito.Mockito.mock
+
+val Kosmos.sideFpsSensorInteractor by
+ Kosmos.Fixture {
+ SideFpsSensorInteractor(
+ applicationContext,
+ fingerprintPropertyRepository,
+ windowManager,
+ displayStateInteractor,
+ Optional.of(fingerprintInteractiveToAuthProvider),
+ biometricSettingsRepository,
+ keyguardTransitionInteractor,
+ mock(SideFpsLogger::class.java),
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index 20fa545d54e0..cccd90832326 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -50,4 +50,11 @@ class FakeCommunalRepository(
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
+
+ private val _communalEnabledState: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val communalEnabledState: StateFlow<Boolean> = _communalEnabledState
+
+ fun setCommunalEnabledState(enabled: Boolean) {
+ _communalEnabledState.value = enabled
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index e25e8c099c21..bc7e7af245a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -39,13 +39,4 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) {
_communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority))
}
-
- private var isHostActive = false
- override fun updateAppWidgetHostActive(active: Boolean) {
- isHostActive = active
- }
-
- fun isHostActive(): Boolean {
- return isHostActive
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 6436a382eb7f..77caeaa6da4d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,25 +16,16 @@
package com.android.systemui.deviceentry.data.repository
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import dagger.Binds
import dagger.Module
import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [DeviceEntryRepository] */
@SysUISingleton
class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
- private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> =
- MutableSharedFlow()
- override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> =
- _enteringDeviceFromBiometricUnlock.asSharedFlow()
-
private var isLockscreenEnabled = true
private val _isBypassEnabled = MutableStateFlow(false)
@@ -62,10 +53,6 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository {
fun setBypassEnabled(isBypassEnabled: Boolean) {
_isBypassEnabled.value = isBypassEnabled
}
-
- suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) {
- _enteringDeviceFromBiometricUnlock.emit(sourceType)
- }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
new file mode 100644
index 000000000000..3070cf4c06ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.authRippleInteractor by
+ Kosmos.Fixture {
+ AuthRippleInteractor(
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
+ deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index de58ae5e9452..878e38594fe1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.deviceEntryHapticsInteractor by
Kosmos.Fixture {
DeviceEntryHapticsInteractor(
- deviceEntryInteractor = deviceEntryInteractor,
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor,
fingerprintPropertyRepository = fingerprintPropertyRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
index 8dcdd3a9425c..0d1a31f9605e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
@@ -28,6 +26,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.sceneContainerFlags
import kotlinx.coroutines.ExperimentalCoroutinesApi
+@ExperimentalCoroutinesApi
val Kosmos.deviceEntryInteractor by
Kosmos.Fixture {
DeviceEntryInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
new file mode 100644
index 000000000000..0b9ec92af2b5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.deviceentry.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.deviceEntrySourceInteractor by
+ Kosmos.Fixture {
+ DeviceEntrySourceInteractor(
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
index 45d39b0d4bc4..cf8f8122dd67 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryKosmos.kt
@@ -19,4 +19,5 @@ package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.biometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.fakeBiometricSettingsRepository by Fixture { FakeBiometricSettingsRepository() }
+val Kosmos.biometricSettingsRepository by Fixture { fakeBiometricSettingsRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
index 6437ef3e50f4..0d20939530de 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryKosmos.kt
@@ -19,6 +19,10 @@ package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+val Kosmos.fakeDeviceEntryFingerprintAuthRepository by Fixture {
FakeDeviceEntryFingerprintAuthRepository()
}
+
+val Kosmos.deviceEntryFingerprintAuthRepository by Fixture {
+ fakeDeviceEntryFingerprintAuthRepository
+}
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 5766f7a9028c..793e2d7efcda 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
@@ -65,7 +65,7 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
private val _isKeyguardUnlocked = MutableStateFlow(false)
- override val isKeyguardUnlocked: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
+ override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
private val _isKeyguardOccluded = MutableStateFlow(false)
override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
@@ -165,7 +165,7 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
_isKeyguardOccluded.value = isOccluded
}
- fun setKeyguardUnlocked(isUnlocked: Boolean) {
+ fun setKeyguardDismissible(isUnlocked: Boolean) {
_isKeyguardUnlocked.value = isUnlocked
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
index 35cfa89e56ed..a8f45b0974c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -26,7 +26,7 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.aodBurnInViewModel by Fixture {
+var Kosmos.aodBurnInViewModel by Fixture {
AodBurnInViewModel(
burnInInteractor = burnInInteractor,
configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
index 5ceefde32d2a..73fd9991945c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
import com.android.systemui.keyguard.domain.interactor.burnInInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -27,6 +28,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import kotlinx.coroutines.ExperimentalCoroutinesApi
val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() }
@@ -34,6 +36,7 @@ val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture {
setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition)
}
+@ExperimentalCoroutinesApi
val Kosmos.deviceEntryIconViewModel by Fixture {
DeviceEntryIconViewModel(
transitions = deviceEntryIconViewModelTransitionsMock,
@@ -46,5 +49,6 @@ val Kosmos.deviceEntryIconViewModel by Fixture {
sceneContainerFlags = sceneContainerFlags,
keyguardViewController = { statusBarKeyguardViewManager },
deviceEntryInteractor = deviceEntryInteractor,
+ deviceEntrySourceInteractor = deviceEntrySourceInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index 7cc5d6b6243a..e13fa5207b33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -14,9 +14,66 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.shade
+import android.view.WindowManager
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.windowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.deviceProvisionedController
+import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.shadeControllerSceneImpl by
+ Kosmos.Fixture {
+ ShadeControllerSceneImpl(
+ scope = applicationCoroutineScope,
+ shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
+ notificationStackScrollLayout = mock<NotificationStackScrollLayout>(),
+ deviceEntryInteractor = deviceEntryInteractor,
+ touchLog = mock<LogBuffer>(),
+ commandQueue = mock<CommandQueue>(),
+ statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
+ notificationShadeWindowController = mock<NotificationShadeWindowController>(),
+ assistManagerLazy = { mock<AssistManager>() },
+ )
+ }
-var Kosmos.shadeController by Kosmos.Fixture { mock<ShadeController>() }
+val Kosmos.shadeControllerImpl by
+ Kosmos.Fixture {
+ ShadeControllerImpl(
+ mock<CommandQueue>(),
+ fakeExecutor,
+ mock<LogBuffer>(),
+ windowRootViewVisibilityInteractor,
+ mock<KeyguardStateController>(),
+ statusBarStateController,
+ statusBarKeyguardViewManager,
+ mock<StatusBarWindowController>(),
+ deviceProvisionedController,
+ mock<NotificationShadeWindowController>(),
+ mock<WindowManager>(),
+ { mock<ShadeViewController>() },
+ { mock<AssistManager>() },
+ { mock<NotificationGutsManager>() },
+ )
+ }
+var Kosmos.shadeController: ShadeController by Kosmos.Fixture { shadeControllerImpl }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
index da956ec67696..da956ec67696 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/EntryUtil.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
index dda7fadde2d7..4efcada96a14 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java
@@ -52,32 +52,38 @@ public class GroupEntryBuilder {
return ge;
}
+ /** Sets the group key. */
public GroupEntryBuilder setKey(String key) {
mKey = key;
return this;
}
+ /** Sets the creation time. */
public GroupEntryBuilder setCreationTime(long creationTime) {
mCreationTime = creationTime;
return this;
}
+ /** Sets the parent entry of the group. */
public GroupEntryBuilder setParent(@Nullable GroupEntry entry) {
mParent = entry;
return this;
}
+ /** Sets the section the group belongs to. */
public GroupEntryBuilder setSection(@Nullable NotifSection section) {
mNotifSection = section;
return this;
}
+ /** Sets the group summary. */
public GroupEntryBuilder setSummary(
NotificationEntry summary) {
mSummary = summary;
return this;
}
+ /** Sets the group children. */
public GroupEntryBuilder setChildren(List<NotificationEntry> children) {
mChildren.clear();
mChildren.addAll(children);
@@ -90,6 +96,7 @@ public class GroupEntryBuilder {
return this;
}
+ /** Get the group's internal children list. */
public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) {
return groupEntry.getRawChildren();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index db4050905200..7c398cd45f90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.lockscreenToOccludedTransitionViewModel
@@ -40,6 +41,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
- lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel
+ lockscreenToGlanceableHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel,
+ aodBurnInViewModel = aodBurnInViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index d80ee758269f..cf800d04f816 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -23,6 +23,8 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
@@ -34,5 +36,7 @@ val Kosmos.windowRootViewVisibilityInteractor by Fixture {
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
activeNotificationsInteractor = activeNotificationsInteractor,
+ sceneInteractorProvider = { sceneInteractor },
+ sceneContainerFlags = sceneContainerFlags,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
new file mode 100644
index 000000000000..370b1773e8f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+val Kosmos.dozeServiceHost: DozeServiceHost by Kosmos.Fixture { mock(DozeServiceHost::class.java) }
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index f5e4af50fc29..16f99e9289db 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -6,6 +6,9 @@ class :aidl stubclass
# Keep all feature flag implementations
class :feature_flags stubclass
+# Keep all sysprops generated code implementations
+class :sysprops stubclass
+
# Collections
class android.util.ArrayMap stubclass
class android.util.ArraySet stubclass
@@ -112,6 +115,12 @@ class android.os.HandlerExecutor stubclass
class android.os.PatternMatcher stubclass
class android.os.ParcelUuid stubclass
+# Logging related interfaces from modules-utils
+class com.android.internal.logging.InstanceId stubclass
+class com.android.internal.logging.InstanceIdSequence stubclass
+class com.android.internal.logging.UiEvent stubclass
+class com.android.internal.logging.UiEventLogger stubclass
+
# XML
class com.android.internal.util.XmlPullParserWrapper stubclass
class com.android.internal.util.XmlSerializerWrapper stubclass
@@ -129,6 +138,9 @@ class com.android.modules.utils.TypedXmlSerializer stubclass
class android.net.Uri stubclass
class android.net.UriCodec stubclass
+# Telephony
+class android.telephony.PinResult stubclass
+
# Just enough to support mocking, no further functionality
class android.content.Context stub
method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index eacdc2f79254..91c522e82cce 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -19,8 +19,6 @@ package android.platform.test.ravenwood;
import android.os.HandlerThread;
import android.os.Looper;
-import java.util.Objects;
-
public class RavenwoodRuleImpl {
private static final String MAIN_THREAD_NAME = "RavenwoodMain";
@@ -31,6 +29,10 @@ public class RavenwoodRuleImpl {
public static void init(RavenwoodRule rule) {
android.os.Process.init$ravenwood(rule.mUid, rule.mPid);
android.os.Binder.init$ravenwood();
+ android.os.SystemProperties.init$ravenwood(
+ rule.mSystemProperties.getValues(),
+ rule.mSystemProperties.getKeyReadablePredicate(),
+ rule.mSystemProperties.getKeyWritablePredicate());
com.android.server.LocalServices.removeAllServicesForTest();
@@ -49,7 +51,8 @@ public class RavenwoodRuleImpl {
com.android.server.LocalServices.removeAllServicesForTest();
- android.os.Process.reset$ravenwood();
+ android.os.SystemProperties.reset$ravenwood();
android.os.Binder.reset$ravenwood();
+ android.os.Process.reset$ravenwood();
}
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 53da8ba14a2c..dd442f08321f 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -62,6 +62,8 @@ public class RavenwoodRule implements TestRule {
boolean mProvideMainThread = false;
+ final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
+
public RavenwoodRule() {
}
@@ -98,6 +100,40 @@ public class RavenwoodRule implements TestRule {
return this;
}
+ /**
+ * Configure the given system property as immutable for the duration of the test.
+ * Read access to the key is allowed, and write access will fail. When {@code value} is
+ * {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyImmutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadOnly(key);
+ return this;
+ }
+
+ /**
+ * Configure the given system property as mutable for the duration of the test.
+ * Both read and write access to the key is allowed, and its value will be reset between
+ * each test. When {@code value} is {@code null}, the value is left as undefined.
+ *
+ * All properties in the {@code debug.*} namespace are automatically mutable, with no
+ * developer action required.
+ *
+ * Has no effect under non-Ravenwood environments.
+ */
+ public Builder setSystemPropertyMutable(/* @NonNull */ String key,
+ /* @Nullable */ Object value) {
+ mRule.mSystemProperties.setValue(key, value);
+ mRule.mSystemProperties.setAccessReadWrite(key);
+ return this;
+ }
+
public RavenwoodRule build() {
return mRule;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
new file mode 100644
index 000000000000..85ad4e444f24
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+class RavenwoodSystemProperties {
+ private final Map<String, String> mValues = new HashMap<>();
+
+ /** Set of additional keys that should be considered readable */
+ private final Set<String> mKeyReadable = new HashSet<>();
+ private final Predicate<String> mKeyReadablePredicate = (key) -> {
+ final String root = getKeyRoot(key);
+
+ if (root.startsWith("debug.")) return true;
+
+ // This set is carefully curated to help identify situations where a test may
+ // accidentally depend on a default value of an obscure property whose owner hasn't
+ // decided how Ravenwood should behave.
+ if (root.startsWith("boot.")) return true;
+ if (root.startsWith("build.")) return true;
+ if (root.startsWith("product.")) return true;
+ if (root.startsWith("soc.")) return true;
+ if (root.startsWith("system.")) return true;
+
+ switch (key) {
+ case "gsm.version.baseband":
+ case "no.such.thing":
+ case "ro.bootloader":
+ case "ro.debuggable":
+ case "ro.hardware":
+ case "ro.hw_timeout_multiplier":
+ case "ro.odm.build.media_performance_class":
+ case "ro.treble.enabled":
+ case "ro.vndk.version":
+ return true;
+ }
+
+ return mKeyReadable.contains(key);
+ };
+
+ /** Set of additional keys that should be considered writable */
+ private final Set<String> mKeyWritable = new HashSet<>();
+ private final Predicate<String> mKeyWritablePredicate = (key) -> {
+ final String root = getKeyRoot(key);
+
+ if (root.startsWith("debug.")) return true;
+
+ return mKeyWritable.contains(key);
+ };
+
+ public RavenwoodSystemProperties() {
+ // TODO: load these values from build.prop generated files
+ setValueForPartitions("product.brand", "Android");
+ setValueForPartitions("product.device", "Ravenwood");
+ setValueForPartitions("product.manufacturer", "Android");
+ setValueForPartitions("product.model", "Ravenwood");
+ setValueForPartitions("product.name", "Ravenwood");
+
+ setValueForPartitions("product.cpu.abilist", "x86_64");
+ setValueForPartitions("product.cpu.abilist32", "");
+ setValueForPartitions("product.cpu.abilist64", "x86_64");
+
+ setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024");
+ setValueForPartitions("build.date.utc", "1704092400");
+ setValueForPartitions("build.id", "MAIN");
+ setValueForPartitions("build.tags", "dev-keys");
+ setValueForPartitions("build.type", "userdebug");
+ setValueForPartitions("build.version.all_codenames", "REL");
+ setValueForPartitions("build.version.codename", "REL");
+ setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101");
+ setValueForPartitions("build.version.known_codenames", "REL");
+ setValueForPartitions("build.version.release", "14");
+ setValueForPartitions("build.version.release_or_codename", "VanillaIceCream");
+ setValueForPartitions("build.version.sdk", "34");
+
+ setValue("ro.board.first_api_level", "1");
+ setValue("ro.product.first_api_level", "1");
+
+ setValue("ro.soc.manufacturer", "Android");
+ setValue("ro.soc.model", "Ravenwood");
+
+ setValue("ro.debuggable", "1");
+ }
+
+ Map<String, String> getValues() {
+ return new HashMap<>(mValues);
+ }
+
+ Predicate<String> getKeyReadablePredicate() {
+ return mKeyReadablePredicate;
+ }
+
+ Predicate<String> getKeyWritablePredicate() {
+ return mKeyWritablePredicate;
+ }
+
+ private static final String[] PARTITIONS = {
+ "bootimage",
+ "odm",
+ "product",
+ "system",
+ "system_ext",
+ "vendor",
+ "vendor_dlkm",
+ };
+
+ /**
+ * Set the given property for all possible partitions where it could be defined. For
+ * example, the value of {@code ro.build.type} is typically also mirrored under
+ * {@code ro.system.build.type}, etc.
+ */
+ private void setValueForPartitions(String key, String value) {
+ setValue("ro." + key, value);
+ for (String partition : PARTITIONS) {
+ setValue("ro." + partition + "." + key, value);
+ }
+ }
+
+ public void setValue(String key, Object value) {
+ final String valueString = (value == null) ? null : String.valueOf(value);
+ if ((valueString == null) || valueString.isEmpty()) {
+ mValues.remove(key);
+ } else {
+ mValues.put(key, valueString);
+ }
+ }
+
+ public void setAccessNone(String key) {
+ mKeyReadable.remove(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadOnly(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadWrite(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.add(key);
+ }
+
+ /**
+ * Return the "root" of the given property key, stripping away any modifier prefix such as
+ * {@code ro.} or {@code persist.}.
+ */
+ private static String getKeyRoot(String key) {
+ if (key.startsWith("ro.")) {
+ return key.substring(3);
+ } else if (key.startsWith("persist.")) {
+ return key.substring(8);
+ } else {
+ return key;
+ }
+ }
+}
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index ab2546bab246..eaf01a32592e 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,6 +1,10 @@
# Only classes listed here can use the Ravenwood annotations.
+com.android.internal.display.BrightnessSynchronizer
com.android.internal.util.ArrayUtils
+com.android.internal.logging.MetricsLogger
+com.android.internal.logging.testing.FakeMetricsLogger
+com.android.internal.logging.testing.UiEventLoggerFake
com.android.internal.os.BatteryStatsHistory
com.android.internal.os.BatteryStatsHistory$TraceDelegate
com.android.internal.os.BatteryStatsHistory$VarintParceler
@@ -28,6 +32,7 @@ android.util.LruCache
android.util.MonthDisplayHelper
android.util.RecurrenceRule
android.util.RotationUtils
+android.util.Singleton
android.util.Slog
android.util.SparseDoubleArray
android.util.SparseSetArray
@@ -47,6 +52,7 @@ android.os.BatteryUsageStatsQuery
android.os.Binder
android.os.Binder$IdentitySupplier
android.os.Broadcaster
+android.os.Build
android.os.BundleMerger
android.os.ConditionVariable
android.os.FileUtils
@@ -65,11 +71,14 @@ android.os.PowerComponents
android.os.Process
android.os.ServiceSpecificException
android.os.SystemClock
+android.os.SystemProperties
android.os.ThreadLocalWorkSource
android.os.TimestampedValue
+android.os.Trace
android.os.UidBatteryConsumer
android.os.UidBatteryConsumer$Builder
android.os.UserHandle
+android.os.UserManager
android.os.WorkSource
android.content.ClipData
@@ -83,16 +92,22 @@ android.content.Intent
android.content.IntentFilter
android.content.UriMatcher
-android.content.pm.PackageInfo
+android.content.pm.ActivityInfo
android.content.pm.ApplicationInfo
-android.content.pm.PackageItemInfo
android.content.pm.ComponentInfo
-android.content.pm.ActivityInfo
-android.content.pm.ServiceInfo
+android.content.pm.PackageInfo
+android.content.pm.PackageItemInfo
+android.content.pm.PackageManager$Flags
+android.content.pm.PackageManager$PackageInfoFlags
+android.content.pm.PackageManager$ApplicationInfoFlags
+android.content.pm.PackageManager$ComponentInfoFlags
+android.content.pm.PackageManager$ResolveInfoFlags
android.content.pm.PathPermission
android.content.pm.ProviderInfo
android.content.pm.ResolveInfo
+android.content.pm.ServiceInfo
android.content.pm.Signature
+android.content.pm.UserInfo
android.database.AbstractCursor
android.database.CharArrayBuffer
@@ -126,6 +141,12 @@ android.graphics.RectF
android.content.ContentProvider
+android.metrics.LogMaker
+
+android.view.Display$HdrCapabilities
+android.view.Display$Mode
+android.view.DisplayInfo
+
com.android.server.LocalServices
com.android.server.power.stats.BatteryStatsImpl
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index 993b2544f110..44682e2088f4 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -45,6 +45,13 @@ flag {
}
flag {
+ name: "fix_drag_pointer_when_ending_drag"
+ namespace: "accessibility"
+ description: "Send the correct pointer id when transitioning from dragging to delegating states."
+ bug: "300002193"
+}
+
+flag {
name: "pinch_zoom_zero_min_span"
namespace: "accessibility"
description: "Whether to set min span of ScaleGestureDetector to zero."
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index c4184854e690..3086ce1ceb40 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -1466,8 +1466,11 @@ public class TouchExplorer extends BaseEventStreamTransformation
int policyFlags = mState.getLastReceivedPolicyFlags();
if (mState.isDragging()) {
// Send an event to the end of the drag gesture.
- mDispatcher.sendMotionEvent(
- event, ACTION_UP, rawEvent, ALL_POINTER_ID_BITS, policyFlags);
+ int pointerIdBits = ALL_POINTER_ID_BITS;
+ if (Flags.fixDragPointerWhenEndingDrag()) {
+ pointerIdBits = 1 << mDraggingPointerId;
+ }
+ mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags);
}
mState.startDelegating();
// Deliver all pointers to the view hierarchy.
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 5c93991bef8c..f914ed54fbb1 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -31,9 +31,12 @@ import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
+import android.service.autofill.ConvertCredentialRequest;
+import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
+import android.service.autofill.IConvertCredentialCallback;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import android.service.autofill.SaveRequest;
@@ -69,6 +72,7 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
private int mPendingFillRequestId = INVALID_REQUEST_ID;
private AtomicReference<IFillCallback> mFillCallback;
private AtomicReference<ISaveCallback> mSaveCallback;
+ private AtomicReference<IConvertCredentialCallback> mConvertCredentialCallback;
private final ComponentName mComponentName;
private final boolean mIsCredentialAutofillService;
@@ -81,13 +85,20 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
extends AbstractRemoteService.VultureCallback<RemoteFillService> {
void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@NonNull String servicePackageName, int requestFlags);
+
void onFillRequestFailure(int requestId, @Nullable CharSequence message);
+
void onFillRequestTimeout(int requestId);
+
void onSaveRequestSuccess(@NonNull String servicePackageName,
@Nullable IntentSender intentSender);
+
// TODO(b/80093094): add timeout here too?
void onSaveRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
+
+ void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+ convertCredentialResponse);
}
RemoteFillService(Context context, ComponentName componentName, int userId,
@@ -211,6 +222,32 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}
}
+ static class IConvertCredentialCallbackDelegate extends IConvertCredentialCallback.Stub {
+
+ private WeakReference<IConvertCredentialCallback> mCallbackWeakRef;
+
+ IConvertCredentialCallbackDelegate(IConvertCredentialCallback callback) {
+ mCallbackWeakRef = new WeakReference(callback);
+ }
+
+ @Override
+ public void onSuccess(ConvertCredentialResponse convertCredentialResponse)
+ throws RemoteException {
+ IConvertCredentialCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onSuccess(convertCredentialResponse);
+ }
+ }
+
+ @Override
+ public void onFailure(CharSequence message) throws RemoteException {
+ IConvertCredentialCallback callback = mCallbackWeakRef.get();
+ if (callback != null) {
+ callback.onFailure(message);
+ }
+ }
+ }
+
/**
* Wraps an {@link IFillCallback} object using weak reference.
*
@@ -237,6 +274,18 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
return callback;
}
+ /**
+ * Wraps an {@link IConvertCredentialCallback} object using weak reference
+ */
+ private IConvertCredentialCallback maybeWrapWithWeakReference(
+ IConvertCredentialCallback callback) {
+ if (remoteFillServiceUseWeakReference()) {
+ mConvertCredentialCallback = new AtomicReference<>(callback);
+ return new IConvertCredentialCallbackDelegate(callback);
+ }
+ return callback;
+ }
+
public void onFillCredentialRequest(@NonNull FillRequest request,
IAutoFillManagerClient autofillCallback) {
if (sVerbose) {
@@ -378,6 +427,52 @@ final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
}));
}
+ public void onConvertCredentialRequest(
+ @NonNull ConvertCredentialRequest convertCredentialRequest) {
+ if (sVerbose) Slog.v(TAG, "calling onConvertCredentialRequest()");
+ CompletableFuture<ConvertCredentialResponse>
+ connectThenConvertCredentialRequest = postAsync(
+ remoteService -> {
+ if (sVerbose) {
+ Slog.v(TAG, "calling onConvertCredentialRequest()");
+ }
+ CompletableFuture<ConvertCredentialResponse>
+ convertCredentialCompletableFuture = new CompletableFuture<>();
+ remoteService.onConvertCredentialRequest(convertCredentialRequest,
+ maybeWrapWithWeakReference(
+ new IConvertCredentialCallback.Stub() {
+ @Override
+ public void onSuccess(ConvertCredentialResponse
+ convertCredentialResponse) {
+ convertCredentialCompletableFuture
+ .complete(convertCredentialResponse);
+ }
+
+ @Override
+ public void onFailure(CharSequence message) {
+ String errorMessage =
+ message == null ? "" :
+ String.valueOf(message);
+ convertCredentialCompletableFuture
+ .completeExceptionally(
+ new RuntimeException(errorMessage));
+ }
+ })
+ );
+ return convertCredentialCompletableFuture;
+ }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+ connectThenConvertCredentialRequest.whenComplete(
+ (res, err) -> Handler.getMain().post(() -> {
+ if (err == null) {
+ mCallbacks.onConvertCredentialRequestSuccess(res);
+ } else {
+ // TODO: Add a callback function to log this failure
+ Slog.e(TAG, "Error calling on convert credential request", err);
+ }
+ }));
+ }
+
public void onSaveRequest(@NonNull SaveRequest request) {
postAsync(service -> {
if (sVerbose) Slog.v(TAG, "calling onSaveRequest()");
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 123470304783..0af703e415ff 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -25,6 +25,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
import android.os.Bundle;
+import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.util.Slog;
@@ -98,6 +99,12 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal
}
+ @Override
+ public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+ convertCredentialResponse) {
+
+ }
+
/**
* Requests a new fill response.
*/
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f3b74ea00a58..049feeed2fa1 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -24,6 +24,7 @@ import static android.service.autofill.Dataset.PICK_REASON_PCC_DETECTION_PREFERR
import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_ONLY;
import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN;
+import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
import static android.service.autofill.FillEventHistory.Event.UI_TYPE_MENU;
@@ -44,6 +45,7 @@ import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_EXITED;
import static android.view.autofill.AutofillManager.COMMIT_REASON_SESSION_DESTROYED;
import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN;
+import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID;
import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM;
import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString;
@@ -130,6 +132,7 @@ import android.service.assist.classification.FieldClassificationResponse;
import android.service.autofill.AutofillFieldClassificationService.Scores;
import android.service.autofill.AutofillService;
import android.service.autofill.CompositeUserData;
+import android.service.autofill.ConvertCredentialResponse;
import android.service.autofill.Dataset;
import android.service.autofill.Dataset.DatasetEligibleReason;
import android.service.autofill.Field;
@@ -2429,6 +2432,29 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
removeFromService();
}
+ // FillServiceCallbacks
+ @Override
+ public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse
+ convertCredentialResponse) {
+ Dataset dataset = convertCredentialResponse.getDataset();
+ Bundle clientState = convertCredentialResponse.getClientState();
+ if (dataset != null) {
+ int requestId = -1;
+ if (clientState != null) {
+ requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID);
+ } else {
+ Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this "
+ + "would cause loss in logging.");
+ }
+ // TODO: Add autofill related logging; consider whether to log the index
+ fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET);
+ } else {
+ // TODO: Add logging to log this error case
+ Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is "
+ + "null");
+ }
+ }
+
/**
* Gets the {@link FillContext} for a request.
*
diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig
index 4022e3378954..1416c888f790 100644
--- a/services/backup/flags.aconfig
+++ b/services/backup/flags.aconfig
@@ -11,7 +11,7 @@ flag {
flag {
name: "enable_metrics_system_backup_agents"
- namespace: "backup"
+ namespace: "onboarding"
description: "Enable SystemBackupAgent to collect B&R agent metrics by passing an instance of "
"the logger to each BackupHelper."
bug: "296844513"
diff --git a/services/companion/TEST_MAPPING b/services/companion/TEST_MAPPING
index 37c47baa813b..ae6d59129adb 100644
--- a/services/companion/TEST_MAPPING
+++ b/services/companion/TEST_MAPPING
@@ -9,5 +9,10 @@
{
"name": "CtsCompanionDeviceManagerNoCompanionServicesTestCases"
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsCompanionDeviceManagerMultiProcessTestCases"
+ }
]
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 2e01ced2022b..5019428c5323 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -368,8 +368,10 @@ public class CompanionDeviceManagerService extends SystemService {
if (blueToothDevices != null) {
for (BluetoothDevice bluetoothDevice : blueToothDevices) {
- final List<ParcelUuid> deviceUuids = bluetoothDevice.getUuids() == null
- ? Collections.emptyList() : Arrays.asList(bluetoothDevice.getUuids());
+ final ParcelUuid[] bluetoothDeviceUuids = bluetoothDevice.getUuids();
+
+ final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+ ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
for (AssociationInfo ai:
mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 7eca1193ca12..c514f3ef29d0 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -38,6 +38,7 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.ObservableUuid;
import com.android.server.companion.ObservableUuidStore;
@@ -172,8 +173,10 @@ public class BluetoothCompanionDeviceConnectionListener
mAssociationStore.getAssociationsByAddress(device.getAddress());
final List<ObservableUuid> observableUuids =
mObservableUuidStore.getObservableUuidsForUser(userId);
- final List<ParcelUuid> deviceUuids = device.getUuids() == null
- ? Collections.emptyList() : Arrays.asList(device.getUuids());
+ final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
+
+ final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+ ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
if (DEBUG) {
Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 3b9d92dc3d02..8962bf02ff2e 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -163,7 +163,7 @@ class InputController {
createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
deviceToken, displayId, phys,
() -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
- mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ setVirtualMousePointerDisplayId(displayId);
}
void createTouchscreen(@NonNull String deviceName, int vendorId, int productId,
@@ -235,8 +235,7 @@ class InputController {
// id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
// removed from the mInputDeviceDescriptors instance variable prior to this point.
if (inputDeviceDescriptor.isMouse()) {
- if (mInputManagerInternal.getVirtualMousePointerDisplayId()
- == inputDeviceDescriptor.getDisplayId()) {
+ if (getVirtualMousePointerDisplayId() == inputDeviceDescriptor.getDisplayId()) {
updateActivePointerDisplayIdLocked();
}
}
@@ -271,6 +270,7 @@ class InputController {
mWindowManager.setDisplayImePolicy(displayId, policy);
}
+ // TODO(b/293587049): Remove after pointer icon refactor is complete.
@GuardedBy("mLock")
private void updateActivePointerDisplayIdLocked() {
InputDeviceDescriptor mostRecentlyCreatedMouse = null;
@@ -285,11 +285,11 @@ class InputController {
}
}
if (mostRecentlyCreatedMouse != null) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
+ setVirtualMousePointerDisplayId(
mostRecentlyCreatedMouse.getDisplayId());
} else {
// All mice have been unregistered
- mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
+ setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
}
}
@@ -349,10 +349,8 @@ class InputController {
if (inputDeviceDescriptor == null) {
return false;
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(),
event.getButtonCode(), event.getAction(), event.getEventTimeNanos());
@@ -380,10 +378,8 @@ class InputController {
if (inputDeviceDescriptor == null) {
return false;
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(),
event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos());
@@ -397,10 +393,8 @@ class InputController {
if (inputDeviceDescriptor == null) {
return false;
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(),
event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos());
@@ -415,12 +409,11 @@ class InputController {
throw new IllegalArgumentException(
"Could not get cursor position for input device for given token");
}
- if (inputDeviceDescriptor.getDisplayId()
- != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
- mInputManagerInternal.setVirtualMousePointerDisplayId(
- inputDeviceDescriptor.getDisplayId());
+ if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) {
+ setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId());
}
- return LocalServices.getService(InputManagerInternal.class).getCursorPosition();
+ return LocalServices.getService(InputManagerInternal.class).getCursorPosition(
+ inputDeviceDescriptor.getDisplayId());
}
}
@@ -847,4 +840,22 @@ class InputController {
/** Returns true if the calling thread is a valid thread for device creation. */
boolean isValidThread();
}
+
+ // TODO(b/293587049): Remove after pointer icon refactor is complete.
+ private void setVirtualMousePointerDisplayId(int displayId) {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ // We no longer need to set the pointer display when pointer choreographer is enabled.
+ return;
+ }
+ mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
+ }
+
+ // TODO(b/293587049): Remove after pointer icon refactor is complete.
+ private int getVirtualMousePointerDisplayId() {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ // We no longer need to get the pointer display when pointer choreographer is enabled.
+ return Display.INVALID_DISPLAY;
+ }
+ return mInputManagerInternal.getVirtualMousePointerDisplayId();
+ }
}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index b6e114087f30..2168cb2043ed 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -583,6 +583,11 @@ public class VirtualDeviceManagerService extends SystemService {
return associationInfo == null ? null : associationInfo.getDisplayName();
}
+ @Override // Binder call
+ public @NonNull List<String> getAllPersistentDeviceIds() {
+ return new ArrayList<>(mLocalService.getAllPersistentDeviceIds());
+ }
+
// Binder call
@Override
public boolean isValidVirtualDeviceId(int deviceId) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9375fb14a6d7..8e35b7455d02 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -139,6 +139,7 @@ java_library_static {
libs: [
"services.net",
+ "android.frameworks.location.altitude-V2-java",
"android.hardware.common-V2-java",
"android.hardware.light-V2.0-java",
"android.hardware.gnss-V2-java",
@@ -160,7 +161,6 @@ java_library_static {
],
static_libs: [
- "android.frameworks.location.altitude-V2-java", // AIDL
"android.frameworks.vibrator-V1-java", // AIDL
"android.hardware.authsecret-V1.0-java",
"android.hardware.authsecret-V1-java",
@@ -214,6 +214,7 @@ java_library_static {
"backup_flags_lib",
"policy_flags_lib",
"net_flags_lib",
+ "stats_flags_lib",
],
javac_shard_size: 50,
javacflags: [
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3de224addf95..ca04e41769ee 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -472,6 +472,8 @@ import com.android.server.pm.pkg.SELinuxUtil;
import com.android.server.pm.snapshot.PackageDataSnapshot;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.sdksandbox.SdkSandboxManagerLocal;
+import com.android.server.stats.pull.StatsPullAtomService;
+import com.android.server.stats.pull.StatsPullAtomServiceInternal;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -1308,6 +1310,8 @@ public class ActivityManagerService extends IActivityManager.Stub
*/
final BatteryStatsService mBatteryStatsService;
+ StatsPullAtomServiceInternal mStatsPullAtomServiceInternal;
+
/**
* Information about component usage
*/
@@ -5087,13 +5091,11 @@ public class ActivityManagerService extends IActivityManager.Stub
intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
| Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- final BroadcastOptions bOptions = mUserController.getTemporaryAppAllowlistBroadcastOptions(
- reason);
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
null, null, AppOpsManager.OP_NONE,
- bOptions.toBundle(), true,
+ null, true,
false, MY_PID, SYSTEM_UID,
SYSTEM_UID, MY_PID, app.userId);
}
@@ -16556,6 +16558,21 @@ public class ActivityManagerService extends IActivityManager.Stub
final @ProcessCapability int capability) {
mBatteryStatsService.noteUidProcessState(uid, state);
mAppOpsService.updateUidProcState(uid, state, capability);
+ if (StatsPullAtomService.ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ try {
+ if (mStatsPullAtomServiceInternal == null) {
+ mStatsPullAtomServiceInternal = LocalServices.getService(
+ StatsPullAtomServiceInternal.class);
+ }
+ if (mStatsPullAtomServiceInternal != null) {
+ mStatsPullAtomServiceInternal.noteUidProcessState(uid, state);
+ } else {
+ Slog.d(TAG, "StatsPullAtomService not ready yet");
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception during logging uid proc state change event", e);
+ }
+ }
if (mTrackingAssociations) {
for (int i1=0, N1=mAssociations.size(); i1<N1; i1++) {
ArrayMap<ComponentName, SparseArray<ArrayMap<String, Association>>> targetComponents
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 57c52c2cf408..45f657d713ad 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3754,6 +3754,11 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
}
diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
index b07d9a6b258c..9c2e69be7685 100644
--- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java
@@ -520,7 +520,7 @@ final class AppBatteryExemptionTracker
/**
* Default value to {@link #mTrackerEnabled}.
*/
- static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = true;
+ static final boolean DEFAULT_BG_BATTERY_EXEMPTION_ENABLED = false;
AppBatteryExemptionPolicy(@NonNull Injector injector,
@NonNull AppBatteryExemptionTracker tracker) {
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 1f98aba5bbd7..fb89b8e4f3b4 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -102,6 +102,11 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onProcessDied(int pid, int uid) {
}
};
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index fa5dbd2543d3..f5c34a5da1c1 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2852,6 +2852,7 @@ public final class ProcessList {
? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
}
}
+ dispatchProcessStarted(app, pid);
checkSlow(app.getStartTime(), "startProcess: done updating pids map");
return true;
}
@@ -4977,6 +4978,22 @@ public final class ProcessList {
}
}
+ void dispatchProcessStarted(ProcessRecord app, int pid) {
+ int i = mProcessObservers.beginBroadcast();
+ while (i > 0) {
+ i--;
+ final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+ if (observer != null) {
+ try {
+ observer.onProcessStarted(pid, app.uid, app.info.uid,
+ app.info.packageName, app.processName);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ mProcessObservers.finishBroadcast();
+ }
+
void dispatchProcessDied(int pid, int uid) {
int i = mProcessObservers.beginBroadcast();
while (i > 0) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index dc14c7aaa0b9..7aafda59298c 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -153,6 +153,7 @@ public class SettingsToPropertiesMapper {
"machine_learning",
"mainline_modularization",
"mainline_sdk",
+ "make_pixel_haptics",
"media_audio",
"media_drm",
"media_reliability",
diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
index 684d6a0fc596..cdd147a0ec37 100644
--- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
+++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java
@@ -177,6 +177,11 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
}
};
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 99b45ec79571..cd295b521e89 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1047,11 +1047,9 @@ public class AudioDeviceBroker {
private void initAudioHalBluetoothState() {
synchronized (mBluetoothAudioStateLock) {
mBluetoothScoOnApplied = false;
- AudioSystem.setParameters("BT_SCO=off");
mBluetoothA2dpSuspendedApplied = false;
- AudioSystem.setParameters("A2dpSuspended=false");
mBluetoothLeSuspendedApplied = false;
- AudioSystem.setParameters("LeAudioSuspended=false");
+ reapplyAudioHalBluetoothState();
}
}
@@ -1114,6 +1112,34 @@ public class AudioDeviceBroker {
}
}
+ @GuardedBy("mBluetoothAudioStateLock")
+ private void reapplyAudioHalBluetoothState() {
+ if (AudioService.DEBUG_COMM_RTE) {
+ Log.v(TAG, "reapplyAudioHalBluetoothState() mBluetoothScoOnApplied: "
+ + mBluetoothScoOnApplied + ", mBluetoothA2dpSuspendedApplied: "
+ + mBluetoothA2dpSuspendedApplied + ", mBluetoothLeSuspendedApplied: "
+ + mBluetoothLeSuspendedApplied);
+ }
+ // Note: the order of parameters is important.
+ if (mBluetoothScoOnApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ AudioSystem.setParameters("BT_SCO=on");
+ } else {
+ AudioSystem.setParameters("BT_SCO=off");
+ if (mBluetoothA2dpSuspendedApplied) {
+ AudioSystem.setParameters("A2dpSuspended=true");
+ } else {
+ AudioSystem.setParameters("A2dpSuspended=false");
+ }
+ if (mBluetoothLeSuspendedApplied) {
+ AudioSystem.setParameters("LeAudioSuspended=true");
+ } else {
+ AudioSystem.setParameters("LeAudioSuspended=false");
+ }
+ }
+ }
+
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
@@ -1775,6 +1801,9 @@ public class AudioDeviceBroker {
initRoutingStrategyIds();
updateActiveCommunicationDevice();
mDeviceInventory.onRestoreDevices();
+ synchronized (mBluetoothAudioStateLock) {
+ reapplyAudioHalBluetoothState();
+ }
mBtHelper.onAudioServerDiedRestoreA2dp();
updateCommunicationRoute("MSG_RESTORE_DEVICES");
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 57b19cda7c12..690c37a9349a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -914,28 +914,27 @@ public class AudioDeviceInventory {
di.mDeviceCodecFormat = codec;
mConnectedDevices.replace(key, di);
codecChange = true;
- }
- final int res = mAudioSystem.handleDeviceConfigChange(
- btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec);
-
- if (res != AudioSystem.AUDIO_STATUS_OK) {
- AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange failed for A2DP device addr="
- + address + " codec="
- + AudioSystem.audioFormatToString(codec))
- .printLog(TAG));
-
- // force A2DP device disconnection in case of error so that AudioService
- // state is consistent with audio policy manager state
- setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
- BluetoothProfile.STATE_DISCONNECTED));
- } else {
- AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM handleDeviceConfigChange success for A2DP device addr="
- + address
- + " codec=" + AudioSystem.audioFormatToString(codec))
- .printLog(TAG));
-
+ final int res = mAudioSystem.handleDeviceConfigChange(
+ btInfo.mAudioSystemDevice, address,
+ BtHelper.getName(btDevice), codec);
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM handleDeviceConfigChange failed for A2DP device addr="
+ + address + " codec="
+ + AudioSystem.audioFormatToString(codec))
+ .printLog(TAG));
+
+ // force A2DP device disconnection in case of error so that AudioService
+ // state is consistent with audio policy manager state
+ setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+ BluetoothProfile.STATE_DISCONNECTED));
+ } else {
+ AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+ "APM handleDeviceConfigChange success for A2DP device addr="
+ + address
+ + " codec=" + AudioSystem.audioFormatToString(codec))
+ .printLog(TAG));
+ }
}
}
if (!codecChange) {
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index a30cdc47a461..9610034caf01 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -1203,7 +1203,7 @@ public class SoundDoseHelper {
@GuardedBy("mCsdStateLock")
private void sanitizeDoseRecords_l() {
if (mDoseRecords.size() > MAX_NUMBER_OF_CACHED_RECORDS) {
- int nrToRemove = MAX_NUMBER_OF_CACHED_RECORDS - mDoseRecords.size();
+ int nrToRemove = mDoseRecords.size() - MAX_NUMBER_OF_CACHED_RECORDS;
Log.w(TAG,
"Removing " + nrToRemove + " records from the total of " + mDoseRecords.size());
// Remove older elements to fit into persisted settings max length
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 8fd2ee2bdc33..3f3540e2868c 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,7 +20,7 @@ package com.android.server.biometrics;
// TODO(b/141025588): Create separate internal and external permissions for AuthService.
// TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
-import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -305,7 +305,7 @@ public class AuthService extends SystemService {
if (promptInfo.containsPrivateApiConfigurations()) {
checkInternalPermission();
}
- if (promptInfo.containsManageBioApiConfigurations()) {
+ if (promptInfo.containsSetLogoApiConfigurations()) {
checkManageBiometricPermission();
}
@@ -439,6 +439,10 @@ public class AuthService extends SystemService {
if (fingerprintService != null) {
fingerprintService.registerAuthenticationStateListener(listener);
}
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ faceService.registerAuthenticationStateListener(listener);
+ }
}
@Override
@@ -449,6 +453,10 @@ public class AuthService extends SystemService {
if (fingerprintService != null) {
fingerprintService.unregisterAuthenticationStateListener(listener);
}
+ final IFaceService faceService = mInjector.getFaceService();
+ if (faceService != null) {
+ faceService.unregisterAuthenticationStateListener(listener);
+ }
}
@Override
@@ -989,8 +997,8 @@ public class AuthService extends SystemService {
}
private void checkManageBiometricPermission() {
- getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG,
- "Must have MANAGE_BIOMETRIC_DIALOG permission");
+ getContext().enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_LOGO,
+ "Must have SET_BIOMETRIC_DIALOG_LOGO permission");
}
private void checkPermission() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
index 58635353c780..1ae4d6465c57 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -91,6 +91,40 @@ public class AuthenticationStateListeners implements IBinder.DeathRecipient {
}
}
+ /**
+ * Defines behavior in response to a successful authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ public void onAuthenticationSucceeded(int requestReason, int userId) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationSucceeded(requestReason, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "succeeded", e);
+ }
+ }
+ }
+
+ /**
+ * Defines behavior in response to a failed authentication
+ * @param requestReason Reason from [BiometricRequestConstants.RequestReason] for the requested
+ * authentication
+ * @param userId The user Id for the requested authentication
+ */
+ public void onAuthenticationFailed(int requestReason, int userId) {
+ for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+ try {
+ listener.onAuthenticationFailed(requestReason, userId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in notifying listener that authentication "
+ + "failed", e);
+ }
+ }
+ }
+
@Override
public void binderDied() {
// Do nothing, handled below
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 73f3999f40ce..321e951ec09b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -24,6 +24,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
+import android.hardware.biometrics.AuthenticationStateListener;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricService;
@@ -63,6 +64,7 @@ import com.android.server.SystemService;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -99,6 +101,8 @@ public class FaceService extends SystemService {
private final BiometricStateCallback<ServiceProvider, FaceSensorPropertiesInternal>
mBiometricStateCallback;
@NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
+ @NonNull
private final FaceProviderFunction mFaceProviderFunction;
@NonNull private final Function<String, FaceProvider> mFaceProvider;
@NonNull
@@ -695,7 +699,8 @@ public class FaceService extends SystemService {
for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
providers.add(
Face10.newInstance(getContext(), mBiometricStateCallback,
- hidlSensor, mLockoutResetDispatcher));
+ mAuthenticationStateListeners, hidlSensor,
+ mLockoutResetDispatcher));
}
return providers;
@@ -830,6 +835,24 @@ public class FaceService extends SystemService {
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
mBiometricStateCallback.registerBiometricStateListener(listener);
}
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void registerAuthenticationStateListener(
+ @NonNull AuthenticationStateListener listener) {
+ super.registerAuthenticationStateListener_enforcePermission();
+
+ mAuthenticationStateListeners.registerAuthenticationStateListener(listener);
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+ @Override
+ public void unregisterAuthenticationStateListener(
+ @NonNull AuthenticationStateListener listener) {
+ super.unregisterAuthenticationStateListener_enforcePermission();
+
+ mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener);
+ }
}
public FaceService(Context context) {
@@ -848,6 +871,7 @@ public class FaceService extends SystemService {
mLockoutResetDispatcher = new LockoutResetDispatcher(context);
mLockPatternUtils = new LockPatternUtils(context);
mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+ mAuthenticationStateListeners = new AuthenticationStateListeners();
mRegistry = new FaceServiceRegistry(mServiceWrapper, biometricServiceSupplier);
mRegistry.addAllRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@Override
@@ -868,8 +892,8 @@ public class FaceService extends SystemService {
try {
final SensorProps[] props = face.getSensorProps();
return new FaceProvider(getContext(),
- mBiometricStateCallback, props, name, mLockoutResetDispatcher,
- BiometricContext.getInstance(getContext()),
+ mBiometricStateCallback, mAuthenticationStateListeners, props, name,
+ mLockoutResetDispatcher, BiometricContext.getInstance(getContext()),
false /* resetLockoutRequiresChallenge */);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -881,7 +905,7 @@ public class FaceService extends SystemService {
if (Flags.deHidl()) {
mFaceProviderFunction = faceProviderFunction != null ? faceProviderFunction :
((filteredSensorProps, resetLockoutRequiresChallenge) -> new FaceProvider(
- getContext(), mBiometricStateCallback,
+ getContext(), mBiometricStateCallback, mAuthenticationStateListeners,
filteredSensorProps.second,
filteredSensorProps.first, mLockoutResetDispatcher,
BiometricContext.getInstance(getContext()),
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 22e399c6747b..f35de93af625 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
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.NotificationManager;
@@ -44,6 +46,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.ClientMonitorCompositeCallback;
@@ -77,6 +80,8 @@ public class FaceAuthenticationClient
private ICancellationSignal mCancellationSignal;
@Nullable
private final SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
@FaceManager.FaceAcquired
private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
@@ -89,11 +94,13 @@ public class FaceAuthenticationClient
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutTracker lockoutCache, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength) {
+ @Authenticators.Types int sensorStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
this(context, lazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, usageStats, lockoutCache, allowBackgroundAuthentication,
- context.getSystemService(SensorPrivacyManager.class), sensorStrength);
+ context.getSystemService(SensorPrivacyManager.class), sensorStrength,
+ authenticationStateListeners);
}
@VisibleForTesting
@@ -107,7 +114,8 @@ public class FaceAuthenticationClient
boolean isStrongBiometric, @NonNull UsageStats usageStats,
@NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
SensorPrivacyManager sensorPrivacyManager,
- @Authenticators.Types int biometricStrength) {
+ @Authenticators.Types int biometricStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */, lockoutTracker,
@@ -118,6 +126,7 @@ public class FaceAuthenticationClient
mNotificationManager = context.getSystemService(NotificationManager.class);
mSensorPrivacyManager = sensorPrivacyManager;
mAuthSessionCoordinator = biometricContext.getAuthSessionCoordinator();
+ mAuthenticationStateListeners = authenticationStateListeners;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -262,6 +271,16 @@ public class FaceAuthenticationClient
0 /* error */,
0 /* vendorError */,
getTargetUserId()));
+
+ if (reportBiometricAuthAttempts()) {
+ if (authenticated) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ } else {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
+ }
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index e4ecf1a61155..d01c2687b1ff 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -59,6 +59,7 @@ import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -103,6 +104,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull
private final BiometricStateCallback mBiometricStateCallback;
@NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
+ @NonNull
private final String mHalInstanceName;
@NonNull
private final Handler mHandler;
@@ -156,18 +159,20 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
public FaceProvider(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull BiometricContext biometricContext,
boolean resetLockoutRequiresChallenge) {
- this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
- biometricContext, null /* daemon */, getHandler(), resetLockoutRequiresChallenge,
- false /* testHalEnabled */);
+ this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
+ lockoutResetDispatcher, biometricContext, null /* daemon */, getHandler(),
+ resetLockoutRequiresChallenge, false /* testHalEnabled */);
}
@VisibleForTesting FaceProvider(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -178,6 +183,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
boolean testHalEnabled) {
mContext = context;
mBiometricStateCallback = biometricStateCallback;
+ mAuthenticationStateListeners = authenticationStateListeners;
mHalInstanceName = halInstanceName;
mFaceSensors = new SensorList<>(ActivityManager.getService());
if (Flags.deHidl()) {
@@ -610,7 +616,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mUsageStats, lockoutTracker,
- allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId),
+ mAuthenticationStateListeners);
scheduleForSensor(sensorId, client, new ClientMonitorCallback() {
@Override
public void onClientStarted(
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 53376669b387..48a676ce4937 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -64,6 +64,7 @@ import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AcquisitionClient;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -119,6 +120,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
@NonNull private final BiometricStateCallback mBiometricStateCallback;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
@NonNull private final Context mContext;
@NonNull private final BiometricScheduler<IBiometricsFace, AidlSession> mScheduler;
@NonNull private final Handler mHandler;
@@ -350,6 +353,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@VisibleForTesting
Face10(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull Handler handler,
@@ -358,6 +362,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
mSensorProperties = sensorProps;
mContext = context;
mBiometricStateCallback = biometricStateCallback;
+ mAuthenticationStateListeners = authenticationStateListeners;
mSensorId = sensorProps.sensorId;
mScheduler = scheduler;
mHandler = handler;
@@ -392,11 +397,12 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
public static Face10 newInstance(@NonNull Context context,
@NonNull BiometricStateCallback biometricStateCallback,
+ @NonNull AuthenticationStateListeners authenticationStateListeners,
@NonNull FaceSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher) {
final Handler handler = new Handler(Looper.getMainLooper());
- return new Face10(context, biometricStateCallback, sensorProps, lockoutResetDispatcher,
- handler, new BiometricScheduler<>(
+ return new Face10(context, biometricStateCallback, authenticationStateListeners,
+ sensorProps, lockoutResetDispatcher, handler, new BiometricScheduler<>(
BiometricScheduler.SENSOR_TYPE_FACE,
null /* gestureAvailabilityTracker */),
BiometricContext.getInstance(context));
@@ -846,7 +852,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector), mBiometricContext,
isStrongBiometric, mUsageStats, mLockoutTracker,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+ mAuthenticationStateListeners);
mScheduler.scheduleClientMonitor(client);
}
@@ -860,7 +867,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
mAuthenticationStatsCollector), mBiometricContext,
isStrongBiometric, mLockoutTracker, mUsageStats,
- allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId));
+ allowBackgroundAuthentication, Utils.getCurrentStrength(mSensorId),
+ mAuthenticationStateListeners);
mScheduler.scheduleClientMonitor(client);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 8ab88923d01e..e44b26399549 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.hidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
@@ -36,6 +38,7 @@ import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -65,6 +68,8 @@ class FaceAuthenticationClient
private int mLastAcquire;
private SensorPrivacyManager mSensorPrivacyManager;
+ @NonNull
+ private final AuthenticationStateListeners mAuthenticationStateListeners;
FaceAuthenticationClient(@NonNull Context context,
@NonNull Supplier<IBiometricsFace> lazyDaemon,
@@ -75,7 +80,8 @@ class FaceAuthenticationClient
@NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
boolean isStrongBiometric, @NonNull LockoutTracker lockoutTracker,
@NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
- @Authenticators.Types int sensorStrength) {
+ @Authenticators.Types int sensorStrength,
+ @NonNull AuthenticationStateListeners authenticationStateListeners) {
super(context, lazyDaemon, token, listener, operationId, restricted,
options, cookie, requireConfirmation, logger, biometricContext,
isStrongBiometric, null /* taskStackListener */,
@@ -84,6 +90,7 @@ class FaceAuthenticationClient
setRequestId(requestId);
mUsageStats = usageStats;
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
+ mAuthenticationStateListeners = authenticationStateListeners;
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -186,6 +193,16 @@ class FaceAuthenticationClient
0 /* error */,
0 /* vendorError */,
getTargetUserId()));
+
+ if (reportBiometricAuthAttempts()) {
+ if (authenticated) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ } else {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
+ }
}
@Override
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 f7e812330ece..6912961ab94b 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
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
import android.annotation.NonNull;
@@ -232,8 +234,16 @@ public class FingerprintAuthenticationClient
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ }
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 4c1d4d6d6d12..7a329e9d69e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.fingerprint.hidl;
+import static android.adaptiveauth.Flags.reportBiometricAuthAttempts;
+
import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
import android.annotation.NonNull;
@@ -142,6 +144,10 @@ class FingerprintAuthenticationClient
if (sidefpsControllerRefactor()) {
mAuthenticationStateListeners.onAuthenticationStopped();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationSucceeded(getRequestReason(),
+ getTargetUserId());
+ }
} else {
mState = STATE_STARTED_PAUSED_ATTEMPTED;
final @LockoutTracker.LockoutMode int lockoutMode =
@@ -161,6 +167,10 @@ class FingerprintAuthenticationClient
onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
cancel();
}
+ if (reportBiometricAuthAttempts()) {
+ mAuthenticationStateListeners.onAuthenticationFailed(getRequestReason(),
+ getTargetUserId());
+ }
}
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 9dd7daf1a1cc..9102cfd0d426 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -286,6 +286,9 @@ final class CompatConfig {
return new CompatChange(changeId);
});
c.addPackageOverride(packageName, overrides, allowedState, versionCode);
+ Slog.d(TAG, (overrides.isEnabled() ? "Enabled" : "Disabled")
+ + " change " + changeId + (c.getName() != null ? " [" + c.getName() + "]" : "")
+ + " for " + packageName);
invalidateCache();
return alreadyKnown.get();
}
@@ -372,7 +375,14 @@ final class CompatConfig {
long changeId = change.getId();
OverrideAllowedState allowedState =
mOverrideValidator.getOverrideAllowedState(changeId, packageName);
- return change.removePackageOverride(packageName, allowedState, versionCode);
+ boolean overrideExists = change.removePackageOverride(packageName, allowedState,
+ versionCode);
+ if (overrideExists) {
+ Slog.d(TAG, "Reset change " + changeId
+ + (change.getName() != null ? " [" + change.getName() + "]" : "")
+ + " for " + packageName + " to default value.");
+ }
+ return overrideExists;
}
/**
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 6ec6a123a4e7..77cb08bc02bd 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -204,6 +204,10 @@ public final class DeviceStateManagerService extends SystemService {
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid, String packageName,
+ String processName) {}
+
+ @Override
public void onProcessDied(int pid, int uid) {}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9cf9119aff82..245fcea06eac 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -2776,17 +2776,17 @@ public final class DisplayManagerService extends SystemService {
}
private ScreenCapture.ScreenshotHardwareBuffer userScreenshotInternal(int displayId) {
+ final ScreenCapture.DisplayCaptureArgs captureArgs;
synchronized (mSyncRoot) {
final IBinder token = getDisplayToken(displayId);
if (token == null) {
return null;
}
- final ScreenCapture.DisplayCaptureArgs captureArgs =
- new ScreenCapture.DisplayCaptureArgs.Builder(token)
+ captureArgs = new ScreenCapture.DisplayCaptureArgs.Builder(token)
.build();
- return ScreenCapture.captureDisplay(captureArgs);
}
+ return ScreenCapture.captureDisplay(captureArgs);
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 380106ba486d..b963a4b614e8 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -87,12 +87,16 @@ public abstract class InputManagerInternal {
* connected, the caller may be blocked for an arbitrary period of time.
*
* @return true if the pointer displayId was set successfully, or false if it fails.
+ *
+ * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
*/
public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId);
/**
* Gets the display id that the MouseCursorController is being forced to target. Returns
* {@link android.view.Display#INVALID_DISPLAY} if there is no override
+ *
+ * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete.
*/
public abstract int getVirtualMousePointerDisplayId();
@@ -101,7 +105,7 @@ public abstract class InputManagerInternal {
*
* Returns NaN-s as the coordinates if the cursor is not available.
*/
- public abstract PointF getCursorPosition();
+ public abstract PointF getCursorPosition(int displayId);
/**
* Enables or disables pointer acceleration for mouse movements.
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 67c23fc4db12..687def05b1d7 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1448,6 +1448,10 @@ public class InputManagerService extends IInputManager.Stub
}
private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ throw new IllegalStateException(
+ "This must not be used when PointerChoreographer is enabled");
+ }
final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY;
// Take care to not make calls to window manager while holding internal locks.
@@ -1486,6 +1490,10 @@ public class InputManagerService extends IInputManager.Stub
}
private int getVirtualMousePointerDisplayId() {
+ if (com.android.input.flags.Flags.enablePointerChoreographer()) {
+ throw new IllegalStateException(
+ "This must not be used when PointerChoreographer is enabled");
+ }
synchronized (mAdditionalDisplayInputPropertiesLock) {
return mOverriddenPointerDisplayId;
}
@@ -3332,8 +3340,8 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public PointF getCursorPosition() {
- final float[] p = mNative.getMouseCursorPosition();
+ public PointF getCursorPosition(int displayId) {
+ final float[] p = mNative.getMouseCursorPosition(displayId);
if (p == null || p.length != 2) {
throw new IllegalStateException("Failed to get mouse cursor position");
}
@@ -3614,6 +3622,13 @@ public class InputManagerService extends IInputManager.Stub
}
/**
+ * Sets Accessibility slow keys threshold in milliseconds.
+ */
+ public void setAccessibilitySlowKeysThreshold(int thresholdTimeMs) {
+ mNative.setAccessibilitySlowKeysThreshold(thresholdTimeMs);
+ }
+
+ /**
* Sets whether Accessibility sticky keys is enabled.
*/
public void setAccessibilityStickyKeysEnabled(boolean enabled) {
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 572d844d752d..165dfe445751 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -89,6 +89,8 @@ class InputSettingsObserver extends ContentObserver {
(reason) -> updateShowRotaryInput()),
Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BOUNCE_KEYS),
(reason) -> updateAccessibilityBounceKeys()),
+ Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SLOW_KEYS),
+ (reason) -> updateAccessibilitySlowKeys()),
Map.entry(Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_STICKY_KEYS),
(reason) -> updateAccessibilityStickyKeys()));
}
@@ -228,6 +230,11 @@ class InputSettingsObserver extends ContentObserver {
InputSettings.getAccessibilityBounceKeysThreshold(mContext));
}
+ private void updateAccessibilitySlowKeys() {
+ mService.setAccessibilitySlowKeysThreshold(
+ InputSettings.getAccessibilitySlowKeysThreshold(mContext));
+ }
+
private void updateAccessibilityStickyKeys() {
mService.setAccessibilityStickyKeysEnabled(
InputSettings.isAccessibilityStickyKeysEnabled(mContext));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 8aec8ca7a23f..bc8207835a6e 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -233,14 +233,15 @@ interface NativeInputManagerService {
void setStylusButtonMotionEventsEnabled(boolean enabled);
/**
- * Get the current position of the mouse cursor.
+ * Get the current position of the mouse cursor on the given display.
*
- * If the mouse cursor is not currently shown, the coordinate values will be NaN-s.
+ * If the mouse cursor is not currently shown, the coordinate values will be NaN-s. Use
+ * {@link android.view.Display#INVALID_DISPLAY} to get the position of the default mouse cursor.
*
* NOTE: This will grab the PointerController's lock, so we must be careful about calling this
* from the InputReader or Display threads, which may result in a deadlock.
*/
- float[] getMouseCursorPosition();
+ float[] getMouseCursorPosition(int displayId);
/** Set whether showing a pointer icon for styluses is enabled. */
void setStylusPointerIconEnabled(boolean enabled);
@@ -257,6 +258,11 @@ interface NativeInputManagerService {
void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
/**
+ * Notify if Accessibility slow keys threshold is changed from InputSettings.
+ */
+ void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+ /**
* Notify if Accessibility sticky keys is enabled/disabled from InputSettings.
*/
void setAccessibilityStickyKeysEnabled(boolean enabled);
@@ -514,7 +520,7 @@ interface NativeInputManagerService {
public native void setStylusButtonMotionEventsEnabled(boolean enabled);
@Override
- public native float[] getMouseCursorPosition();
+ public native float[] getMouseCursorPosition(int displayId);
@Override
public native void setStylusPointerIconEnabled(boolean enabled);
@@ -526,6 +532,9 @@ interface NativeInputManagerService {
public native void setAccessibilityBounceKeysThreshold(int thresholdTimeMs);
@Override
+ public native void setAccessibilitySlowKeysThreshold(int thresholdTimeMs);
+
+ @Override
public native void setAccessibilityStickyKeysEnabled(boolean enabled);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
index 21b952bb7760..ece236a5f18c 100644
--- a/services/core/java/com/android/server/inputmethod/ClientController.java
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -21,8 +21,6 @@ import android.content.pm.PackageManagerInternal;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
-import android.util.SparseArray;
-import android.view.inputmethod.InputBinding;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -156,48 +154,4 @@ final class ClientController {
return InputMethodUtils.checkIfPackageBelongsToUid(
mPackageManagerInternal, cs.mUid, packageName);
}
-
- static final class ClientState {
- final IInputMethodClientInvoker mClient;
- final IRemoteInputConnection mFallbackInputConnection;
- final int mUid;
- final int mPid;
- final int mSelfReportedDisplayId;
- final InputBinding mBinding;
- final IBinder.DeathRecipient mClientDeathRecipient;
-
- @GuardedBy("ImfLock.class")
- boolean mSessionRequested;
-
- @GuardedBy("ImfLock.class")
- boolean mSessionRequestedForAccessibility;
-
- @GuardedBy("ImfLock.class")
- InputMethodManagerService.SessionState mCurSession;
-
- @GuardedBy("ImfLock.class")
- SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
- new SparseArray<>();
-
- @Override
- public String toString() {
- return "ClientState{" + Integer.toHexString(
- System.identityHashCode(this)) + " mUid=" + mUid
- + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
- }
-
- ClientState(IInputMethodClientInvoker client,
- IRemoteInputConnection fallbackInputConnection,
- int uid, int pid, int selfReportedDisplayId,
- IBinder.DeathRecipient clientDeathRecipient) {
- mClient = client;
- mFallbackInputConnection = fallbackInputConnection;
- mUid = uid;
- mPid = pid;
- mSelfReportedDisplayId = selfReportedDisplayId;
- mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
- mPid);
- mClientDeathRecipient = clientDeathRecipient;
- }
- }
}
diff --git a/services/core/java/com/android/server/inputmethod/ClientState.java b/services/core/java/com/android/server/inputmethod/ClientState.java
new file mode 100644
index 000000000000..e98a5a73ab90
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ClientState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import android.os.IBinder;
+import android.util.SparseArray;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+final class ClientState {
+ final IInputMethodClientInvoker mClient;
+ final IRemoteInputConnection mFallbackInputConnection;
+ final int mUid;
+ final int mPid;
+ final int mSelfReportedDisplayId;
+ final InputBinding mBinding;
+ final IBinder.DeathRecipient mClientDeathRecipient;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequested;
+
+ @GuardedBy("ImfLock.class")
+ boolean mSessionRequestedForAccessibility;
+
+ @GuardedBy("ImfLock.class")
+ InputMethodManagerService.SessionState mCurSession;
+
+ @GuardedBy("ImfLock.class")
+ SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+ new SparseArray<>();
+
+ @Override
+ public String toString() {
+ return "ClientState{" + Integer.toHexString(
+ System.identityHashCode(this)) + " mUid=" + mUid
+ + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+ }
+
+ ClientState(IInputMethodClientInvoker client,
+ IRemoteInputConnection fallbackInputConnection,
+ int uid, int pid, int selfReportedDisplayId,
+ IBinder.DeathRecipient clientDeathRecipient) {
+ mClient = client;
+ mFallbackInputConnection = fallbackInputConnection;
+ mUid = uid;
+ mPid = pid;
+ mSelfReportedDisplayId = selfReportedDisplayId;
+ mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+ mPid);
+ mClientDeathRecipient = clientDeathRecipient;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 50340d241347..4767ebd0aab0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -48,7 +48,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
-import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -116,7 +115,6 @@ import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -282,8 +280,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@NonNull
private InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
- private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
- new SparseBooleanArray(0);
final WindowManagerInternal mWindowManagerInternal;
private final ActivityManagerInternal mActivityManagerInternal;
final PackageManagerInternal mPackageManagerInternal;
@@ -1355,13 +1351,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
clearPackageChangeState();
}
- @Override
- public void onUidRemoved(int uid) {
- synchronized (ImfLock.class) {
- mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.delete(uid);
- }
- }
-
private void clearPackageChangeState() {
// No need to lock them because we access these fields only on getRegisteredHandler().
mChangedPackages.clear();
@@ -2176,7 +2165,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
/**
* Hide the IME if the removed user is the current user.
*/
- private void onClientRemoved(ClientController.ClientState client) {
+ private void onClientRemoved(ClientState client) {
synchronized (ImfLock.class) {
clearClientSessionLocked(client);
clearClientSessionForAccessibilityLocked(client);
@@ -2400,16 +2389,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@StartInputReason int startInputReason,
int unverifiedTargetSdkVersion,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- String selectedMethodId = getSelectedMethodIdLocked();
-
- if (!mSystemReady) {
- // If the system is not yet ready, we shouldn't be running third
- // party code.
- return new InputBindResult(
- InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
- null, null, null, selectedMethodId, getSequenceNumberLocked(), false);
- }
-
if (!InputMethodUtils.checkIfPackageBelongsToUid(mPackageManagerInternal, cs.mUid,
editorInfo.packageName)) {
Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
@@ -2430,6 +2409,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
// Potentially override the selected input method if the new display belongs to a virtual
// device with a custom IME.
+ String selectedMethodId = getSelectedMethodIdLocked();
if (oldDisplayIdToShowIme != mDisplayIdToShowIme) {
final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId);
if (deviceMethodId == null) {
@@ -3674,7 +3654,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
+ "specified for cross-user startInputOrWindowGainedFocus()");
}
}
-
if (windowToken == null) {
Slog.e(TAG, "windowToken cannot be null.");
return InputBindResult.NULL;
@@ -3686,6 +3665,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
"InputMethodManagerService#startInputOrWindowGainedFocus");
final InputBindResult result;
synchronized (ImfLock.class) {
+ if (!mSystemReady) {
+ // If the system is not yet ready, we shouldn't be running third arty code.
+ return new InputBindResult(
+ InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
+ null /* method */, null /* accessibilitySessions */, null /* channel */,
+ getSelectedMethodIdLocked(), getSequenceNumberLocked(),
+ false /* isInputMethodSuppressingSpellChecker */);
+ }
final long ident = Binder.clearCallingIdentity();
try {
result = startInputOrWindowGainedFocusInternalLocked(startInputReason,
@@ -4277,10 +4264,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
synchronized (ImfLock.class) {
if (!canInteractWithImeLocked(callingUid, client,
"getInputMethodWindowVisibleHeight", null /* statsToken */)) {
- if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) {
- EventLog.writeEvent(0x534e4554, "204906124", callingUid, "");
- mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true);
- }
return 0;
}
// This should probably use the caller's display id, but because this is unsupported
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 403b421639cb..71a9f54c0b1c 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -248,6 +248,7 @@ class GnssNetworkConnectivityHandler {
SubscriptionManager subManager = mContext.getSystemService(SubscriptionManager.class);
TelephonyManager telManager = mContext.getSystemService(TelephonyManager.class);
if (subManager != null && telManager != null) {
+ subManager = subManager.createForAllUserProfiles();
List<SubscriptionInfo> subscriptionInfoList =
subManager.getActiveSubscriptionInfoList();
HashSet<Integer> activeSubIds = new HashSet<Integer>();
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index 5e38bca78a7c..2522f7b82353 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -27,6 +27,7 @@ import static java.lang.Math.max;
import android.annotation.Nullable;
import android.location.Location;
+import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.provider.ProviderRequest;
import android.os.SystemClock;
@@ -179,6 +180,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation
private void onThrottlingChangedLocked(boolean deliverImmediate) {
long throttlingIntervalMs = INTERVAL_DISABLED;
if (mDeviceStationary && mDeviceIdle && !mIncomingRequest.isLocationSettingsIgnored()
+ && mIncomingRequest.getQuality() != LocationRequest.QUALITY_HIGH_ACCURACY
&& mLastLocation != null
&& mLastLocation.getElapsedRealtimeAgeMillis(mDeviceStationaryRealtimeMs)
<= MAX_STATIONARY_LOCATION_AGE_MS) {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2cd3ab1ddbbb..1d516e2931d7 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -287,10 +287,14 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onSessionActiveStateChanged(record);
}
- setForegroundServiceAllowance(
- record,
- /* allowRunningInForeground= */ record.isActive()
- && (playbackState == null || playbackState.isActive()));
+ boolean allowRunningInForeground = record.isActive()
+ && (playbackState == null || playbackState.isActive());
+
+ Log.d(TAG, "onSessionActiveStateChanged: "
+ + "record=" + record
+ + "playbackState=" + playbackState
+ + "allowRunningInForeground=" + allowRunningInForeground);
+ setForegroundServiceAllowance(record, allowRunningInForeground);
mHandler.postSessionsChanged(record);
}
}
@@ -388,10 +392,12 @@ public class MediaSessionService extends SystemService implements Monitor {
}
user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
if (playbackState != null) {
- setForegroundServiceAllowance(
- record,
- /* allowRunningInForeground= */ playbackState.isActive()
- && record.isActive());
+ boolean allowRunningInForeground = playbackState.isActive() && record.isActive();
+ Log.d(TAG, "onSessionPlaybackStateChanged: "
+ + "record=" + record
+ + "playbackState=" + playbackState
+ + "allowRunningInForeground=" + allowRunningInForeground);
+ setForegroundServiceAllowance(record, allowRunningInForeground);
}
}
}
@@ -556,6 +562,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
session.close();
+
+ Log.d(TAG, "destroySessionLocked: record=" + session);
setForegroundServiceAllowance(session, /* allowRunningInForeground= */ false);
mHandler.postSessionsChanged(session);
}
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index df612e63927f..bbe6d3a0c8fa 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.media.MediaMetrics;
import android.media.metrics.BundleSession;
+import android.media.metrics.EditingEndedEvent;
import android.media.metrics.IMediaMetricsManager;
import android.media.metrics.NetworkEvent;
import android.media.metrics.PlaybackErrorEvent;
@@ -346,6 +347,24 @@ public final class MediaMetricsManagerService extends SystemService {
StatsLog.write(statsEvent);
}
+ @Override
+ public void reportEditingEndedEvent(String sessionId, EditingEndedEvent event, int userId) {
+ int level = loggingLevel();
+ if (level == LOGGING_LEVEL_BLOCKED) {
+ return;
+ }
+ StatsEvent statsEvent =
+ StatsEvent.newBuilder()
+ .setAtomId(798)
+ .writeString(sessionId)
+ .writeInt(event.getFinalState())
+ .writeInt(event.getErrorCode())
+ .writeLong(event.getTimeSinceCreatedMillis())
+ .usePooledBuffer()
+ .build();
+ StatsLog.write(statsEvent);
+ }
+
private int loggingLevel() {
synchronized (mLock) {
int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 550aed51c8e2..978f46808e3b 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -214,6 +214,11 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override
+ public void onProcessStarted(int pid, int processUid, int packageUid,
+ String packageName, String processName) {
+ }
+
+ @Override
public void onForegroundServicesChanged(int pid, int uid, int serviceTypes) {
MediaProjectionManagerService.this.handleForegroundServicesChanged(pid, uid,
serviceTypes);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 85c4ffe6ac67..f852b8173f30 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -57,6 +57,7 @@ import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
@@ -71,7 +72,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.EventLogTags;
import com.android.server.lights.LightsManager;
import com.android.server.lights.LogicalLight;
-import com.android.server.notification.Flags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -81,6 +81,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* NotificationManagerService helper for handling notification attention effects:
@@ -100,6 +101,20 @@ public final class NotificationAttentionHelper {
private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
private static final int DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED = 0;
+ @VisibleForTesting
+ static final Set<String> NOTIFICATION_AVALANCHE_TRIGGER_INTENTS = Set.of(
+ Intent.ACTION_AIRPLANE_MODE_CHANGED,
+ Intent.ACTION_BOOT_COMPLETED,
+ Intent.ACTION_USER_SWITCHED,
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE
+ );
+
+ @VisibleForTesting
+ static final Map<String, Pair<String, Boolean>> NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS = Map.of(
+ Intent.ACTION_AIRPLANE_MODE_CHANGED, new Pair<>("state", false),
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
+ );
+
private final Context mContext;
private final PackageManager mPackageManager;
private final TelephonyManager mTelephonyManager;
@@ -191,7 +206,7 @@ public final class NotificationAttentionHelper {
mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
if (Flags.politeNotifications()) {
- mStrategy = getPolitenessStrategy();
+ mStrategy = createPolitenessStrategy();
} else {
mStrategy = null;
}
@@ -200,7 +215,7 @@ public final class NotificationAttentionHelper {
loadUserSettings();
}
- private PolitenessStrategy getPolitenessStrategy() {
+ private PolitenessStrategy createPolitenessStrategy() {
if (Flags.crossAppPoliteNotifications()) {
PolitenessStrategy appStrategy = new StrategyPerApp(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
@@ -209,11 +224,12 @@ public final class NotificationAttentionHelper {
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
- return new StrategyGlobal(
+ return new StrategyAvalanche(
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+ mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT),
appStrategy);
} else {
return new StrategyPerApp(
@@ -225,6 +241,11 @@ public final class NotificationAttentionHelper {
}
}
+ @VisibleForTesting
+ PolitenessStrategy getPolitenessStrategy() {
+ return mStrategy;
+ }
+
public void onSystemReady() {
mSystemReady = true;
@@ -259,6 +280,11 @@ public final class NotificationAttentionHelper {
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ if (Flags.crossAppPoliteNotifications()) {
+ for (String avalancheIntent : NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ filter.addAction(avalancheIntent);
+ }
+ }
mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
mContext.getContentResolver().registerContentObserver(
@@ -1052,7 +1078,8 @@ public final class NotificationAttentionHelper {
}
}
- abstract private static class PolitenessStrategy {
+ @VisibleForTesting
+ abstract static class PolitenessStrategy {
static final int POLITE_STATE_DEFAULT = 0;
static final int POLITE_STATE_POLITE = 1;
static final int POLITE_STATE_MUTED = 2;
@@ -1079,6 +1106,8 @@ public final class NotificationAttentionHelper {
protected boolean mApplyPerPackage;
protected final Map<String, Long> mLastUpdatedTimestampByPackage;
+ protected boolean mIsActive = true;
+
public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
int volumeMuted) {
mVolumeStates = new HashMap<>();
@@ -1218,6 +1247,10 @@ public final class NotificationAttentionHelper {
}
return nextState;
}
+
+ boolean isActive() {
+ return mIsActive;
+ }
}
// TODO b/270456865: Only one of the two strategies will be released.
@@ -1289,55 +1322,60 @@ public final class NotificationAttentionHelper {
}
/**
- * Global (cross-app) strategy.
+ * Avalanche (cross-app) strategy.
*/
- private static class StrategyGlobal extends PolitenessStrategy {
+ private static class StrategyAvalanche extends PolitenessStrategy {
private static final String COMMON_KEY = "cross_app_common_key";
private final PolitenessStrategy mAppStrategy;
private long mLastNotificationTimestamp = 0;
- public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
- int volumeMuted, PolitenessStrategy appStrategy) {
+ private final int mTimeoutAvalanche;
+ private long mLastAvalancheTriggerTimestamp = 0;
+
+ StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite,
+ int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) {
super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
+ mTimeoutAvalanche = timeoutAvalanche;
mAppStrategy = appStrategy;
if (DEBUG) {
- Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
+ Log.i(TAG, "StrategyAvalanche: " + timeoutPolite + " " + timeoutMuted + " "
+ + timeoutAvalanche);
}
}
@Override
void onNotificationPosted(NotificationRecord record) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
+ if (isAvalancheActive()) {
+ if (shouldIgnoreNotification(record)) {
+ return;
+ }
- long timeSinceLastNotif =
+ long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
- final String key = getChannelKey(record);
- @PolitenessState final int currState = getPolitenessState(record);
- @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
+ final String key = getChannelKey(record);
+ @PolitenessState final int currState = getPolitenessState(record);
+ @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
- if (DEBUG) {
- Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
- + " vol state: " + nextState + " key: " + key);
- }
+ if (DEBUG) {
+ Log.i(TAG,
+ "StrategyAvalanche onNotificationPosted time delta: "
+ + timeSinceLastNotif
+ + " vol state: " + nextState + " key: " + key);
+ }
- mVolumeStates.put(key, nextState);
+ mVolumeStates.put(key, nextState);
+ }
mAppStrategy.onNotificationPosted(record);
}
@Override
public float getSoundVolume(final NotificationRecord record) {
- final @PolitenessState int globalVolState = getPolitenessState(record);
- final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
-
- // Prioritize the most polite outcome
- if (globalVolState > appVolState) {
+ if (isAvalancheActive()) {
return super.getSoundVolume(record);
} else {
return mAppStrategy.getSoundVolume(record);
@@ -1382,6 +1420,24 @@ public final class NotificationAttentionHelper {
super.setApplyCooldownPerPackage(applyPerPackage);
mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
}
+
+ boolean isAvalancheActive() {
+ mIsActive = (System.currentTimeMillis() - mLastAvalancheTriggerTimestamp
+ < mTimeoutAvalanche);
+ if (DEBUG) {
+ Log.i(TAG, "StrategyAvalanche: active " + mIsActive);
+ }
+ return mIsActive;
+ }
+
+ @Override
+ boolean isActive() {
+ return isAvalancheActive();
+ }
+
+ void setTriggerTimeMs(long timestamp) {
+ mLastAvalancheTriggerTimestamp = timestamp;
+ }
}
//====================== Observers =============================
@@ -1415,6 +1471,30 @@ public final class NotificationAttentionHelper {
|| action.equals(Intent.ACTION_USER_UNLOCKED)) {
loadUserSettings();
}
+
+ if (Flags.crossAppPoliteNotifications()) {
+ if (NOTIFICATION_AVALANCHE_TRIGGER_INTENTS.contains(action)) {
+ boolean enableAvalancheStrategy = true;
+ // Some actions must also match extras, ie. airplane mode => disabled
+ Pair<String, Boolean> expectedExtras =
+ NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS.get(action);
+ if (expectedExtras != null) {
+ enableAvalancheStrategy =
+ intent.getBooleanExtra(expectedExtras.first, false)
+ == expectedExtras.second;
+ }
+
+ if (DEBUG) {
+ Log.i(TAG, "Avalanche trigger intent received: " + action
+ + ". Enabling avalanche strategy: " + enableAvalancheStrategy);
+ }
+
+ if (enableAvalancheStrategy && mStrategy instanceof StrategyAvalanche) {
+ ((StrategyAvalanche) mStrategy)
+ .setTriggerTimeMs(System.currentTimeMillis());
+ }
+ }
+ }
}
};
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c067fa068b12..923be56dd545 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1183,7 +1183,7 @@ public class ZenModeHelper {
!= newPolicy.getPriorityConversationSenders()) {
userModifiedFields |= ZenPolicy.FIELD_CONVERSATIONS;
}
- if (oldPolicy.getPriorityChannels() != newPolicy.getPriorityChannels()) {
+ if (oldPolicy.getPriorityChannelsAllowed() != newPolicy.getPriorityChannelsAllowed()) {
userModifiedFields |= ZenPolicy.FIELD_ALLOW_CHANNELS;
}
if (oldPolicy.getPriorityCategoryReminders()
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 1660c3ef952a..8452c0e61a81 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -152,14 +152,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
@RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
conditional = true)
void ensureCallerPreviouslyGeneratedFile(
- Context context, Pair<Integer, String> callingInfo, int userId,
- String bugreportFile, boolean forceUpdateMapping) {
+ Context context, PackageManager packageManager, Pair<Integer, String> callingInfo,
+ int userId, String bugreportFile, boolean forceUpdateMapping) {
synchronized (mLock) {
if (onboardingBugreportV2Enabled()) {
final int uidForUser = Binder.withCleanCallingIdentity(() -> {
try {
- return context.getPackageManager()
- .getPackageUidAsUser(callingInfo.second, userId);
+ return packageManager.getPackageUidAsUser(callingInfo.second, userId);
} catch (PackageManager.NameNotFoundException exception) {
throwInvalidBugreportFileForCallerException(
bugreportFile, callingInfo.second);
@@ -441,8 +440,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
try {
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile,
- /* forceUpdateMapping= */ false);
+ mContext, mContext.getPackageManager(), new Pair<>(callingUid, callingPackage),
+ userId, bugreportFile, /* forceUpdateMapping= */ false);
} catch (IllegalArgumentException e) {
Slog.e(TAG, e.getMessage());
reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
diff --git a/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
new file mode 100644
index 000000000000..baa41a55c519
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BACKGROUND_INSTALL_OWNERS
@@ -0,0 +1,2 @@
+georgechan@google.com
+wenhaowang@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index f311034a4dd2..ada79aed9d16 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4217,8 +4217,10 @@ final class InstallPackageHelper {
}
}
+ final long firstInstallTime = Flags.fixSystemAppsFirstInstallTime()
+ ? System.currentTimeMillis() : 0;
final ScanResult scanResult = scanPackageNewLI(parsedPackage, parseFlags,
- scanFlags | SCAN_UPDATE_SIGNATURE, 0 /* currentTime */, user, null);
+ scanFlags | SCAN_UPDATE_SIGNATURE, firstInstallTime, user, null);
return new Pair<>(scanResult, shouldHideSystemApp);
}
diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS
index 84324f2524fc..c8bc56ce7dcd 100644
--- a/services/core/java/com/android/server/pm/OWNERS
+++ b/services/core/java/com/android/server/pm/OWNERS
@@ -51,3 +51,5 @@ per-file ShortcutRequestPinProcessor.java = omakoto@google.com, yamasani@google.
per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
+# background install control service
+per-file BackgroundInstall* = file:BACKGROUND_INSTALL_OWNERS \ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index f02eeeab958e..68f6ca1c019f 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -88,6 +88,13 @@ final class PackageHandler extends Handler {
final boolean didRestore = (msg.arg2 != 0);
mPm.mRunningInstalls.delete(msg.arg1);
+ if (request == null) {
+ if (DEBUG_INSTALL) {
+ Slog.i(TAG, "InstallRequest is null. Nothing to do for post-install "
+ + "token " + msg.arg1);
+ }
+ break;
+ }
request.closeFreezer();
request.onInstallCompleted();
request.runPostInstallRunnable();
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
index 18caafdaa56a..f3b146464864 100644
--- a/services/core/java/com/android/server/pm/PreferredComponent.java
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -28,7 +28,8 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
-import com.android.server.pm.pkg.PackageUserState;
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -218,11 +219,15 @@ public class PreferredComponent {
continue;
}
- // Avoid showing the disambiguation dialog if the package which is installed with
- // reason INSTALL_REASON_DEVICE_SETUP.
- final PackageUserState pkgUserState =
- pmi.getPackageStateInternal(ai.packageName).getUserStates().get(userId);
- if (pkgUserState != null && pkgUserState.getInstallReason()
+ // Avoid showing the disambiguation dialog if the package is not installed or
+ // installed with reason INSTALL_REASON_DEVICE_SETUP.
+ final PackageStateInternal ps = pmi.getPackageStateInternal(ai.packageName);
+ if (ps == null) {
+ continue;
+ }
+ final PackageUserStateInternal pkgUserState = ps.getUserStates().get(userId);
+ if (pkgUserState == null
+ || pkgUserState.getInstallReason()
== PackageManager.INSTALL_REASON_DEVICE_SETUP) {
continue;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a97652c8e167..7e3254de2385 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3437,7 +3437,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
}
str.close();
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
// Remove corrupted file and retry.
atomicFile.failRead(str, e);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a6598d602d01..c0596bb10823 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -295,8 +295,6 @@ public class UserManagerService extends IUserManager.Stub {
private static final int USER_VERSION = 11;
- private static final int MAX_USER_STRING_LENGTH = 500;
-
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
static final int WRITE_USER_MSG = 1;
@@ -4692,16 +4690,18 @@ public class UserManagerService extends IUserManager.Stub {
if (userData.persistSeedData) {
if (userData.seedAccountName != null) {
serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME,
- truncateString(userData.seedAccountName));
+ truncateString(userData.seedAccountName,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH));
}
if (userData.seedAccountType != null) {
serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE,
- truncateString(userData.seedAccountType));
+ truncateString(userData.seedAccountType,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH));
}
}
if (userInfo.name != null) {
serializer.startTag(null, TAG_NAME);
- serializer.text(truncateString(userInfo.name));
+ serializer.text(truncateString(userInfo.name, UserManager.MAX_USER_NAME_LENGTH));
serializer.endTag(null, TAG_NAME);
}
synchronized (mRestrictionsLock) {
@@ -4765,11 +4765,11 @@ public class UserManagerService extends IUserManager.Stub {
serializer.endDocument();
}
- private String truncateString(String original) {
- if (original == null || original.length() <= MAX_USER_STRING_LENGTH) {
+ private String truncateString(String original, int limit) {
+ if (original == null || original.length() <= limit) {
return original;
}
- return original.substring(0, MAX_USER_STRING_LENGTH);
+ return original.substring(0, limit);
}
/*
@@ -5236,7 +5236,7 @@ public class UserManagerService extends IUserManager.Stub {
@UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t, @Nullable Object token)
throws UserManager.CheckedUserOperationException {
- String truncatedName = truncateString(name);
+ String truncatedName = truncateString(name, UserManager.MAX_USER_NAME_LENGTH);
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
if (userTypeDetails == null) {
throwCheckedUserOperationException(
@@ -6821,9 +6821,14 @@ public class UserManagerService extends IUserManager.Stub {
Slog.e(LOG_TAG, "No such user for settings seed data u=" + userId);
return;
}
- userData.seedAccountName = truncateString(accountName);
- userData.seedAccountType = truncateString(accountType);
- userData.seedAccountOptions = accountOptions;
+ userData.seedAccountName = truncateString(accountName,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH);
+ userData.seedAccountType = truncateString(accountType,
+ UserManager.MAX_ACCOUNT_STRING_LENGTH);
+ if (accountOptions != null && accountOptions.isBundleContentsWithinLengthLimit(
+ UserManager.MAX_ACCOUNT_OPTIONS_LENGTH)) {
+ userData.seedAccountOptions = accountOptions;
+ }
userData.persistSeedData = persist;
}
if (persist) {
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index f0ff85df13d1..dd2b409c7100 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -357,7 +357,8 @@ final class VerifyingSession {
verifierUser = UserHandle.of(mPm.mUserManager.getCurrentUserId());
}
// TODO(b/300965895): Remove when inconsistencies loading classpaths from apex for
- // user > 1 are fixed.
+ // user > 1 are fixed. Tests should cover verifiers from apex classpaths run on
+ // primary user, secondary user and work profile.
if (pkgLite.isSdkLibrary) {
verifierUser = UserHandle.SYSTEM;
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d0fe9647618a..6ed2d3126455 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -421,6 +421,11 @@ public class PackageInfoUtils {
if (ai.isArchived) {
ai.nonLocalizedLabel = state.getArchiveState().getActivityInfos().get(0).getTitle();
}
+ if (!state.isInstalled() && !state.dataExists()
+ && android.content.pm.Flags.nullableDataDir()) {
+ // The data dir has been deleted
+ ai.dataDir = null;
+ }
}
@Nullable
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0abf304c34ee..1fdcc64a90c8 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2185,6 +2185,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
TalkbackShortcutController getTalkbackShortcutController() {
return new TalkbackShortcutController(mContext);
}
+
+ WindowWakeUpPolicy getWindowWakeUpPolicy() {
+ return new WindowWakeUpPolicy(mContext);
+ }
}
/** {@inheritDoc} */
@@ -2433,7 +2437,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
mTalkbackShortcutController = injector.getTalkbackShortcutController();
- mWindowWakeUpPolicy = new WindowWakeUpPolicy(mContext);
+ mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
initKeyCombinationRules();
initSingleKeyGestureRules(injector.getLooper());
mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index c8c16db15e0e..f5dfb5cc3afe 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -4,7 +4,7 @@ package: "com.android.server.power.feature.flags"
flag {
name: "enable_early_screen_timeout_detector"
- namespace: "power_manager"
+ namespace: "power"
description: "Feature flag for Early Screen Timeout detector"
bug: "309861917"
is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/selinux/QuotaLimiter.java b/services/core/java/com/android/server/selinux/QuotaLimiter.java
new file mode 100644
index 000000000000..e89ddfd2627c
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/QuotaLimiter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.selinux;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * A QuotaLimiter allows to define a maximum number of Atom pushes within a specific time window.
+ *
+ * <p>The limiter divides the time line in windows of a fixed size. Every time a new permit is
+ * requested, the limiter checks whether the previous request was in the same time window as the
+ * current one. If the two windows are the same, it grants a permit only if the number of permits
+ * granted within the window does not exceed the quota. If the two windows are different, it resets
+ * the quota.
+ */
+public class QuotaLimiter {
+
+ private final Clock mClock;
+ private final Duration mWindowSize;
+ private final int mMaxPermits;
+
+ private long mCurrentWindow = 0;
+ private int mPermitsGranted = 0;
+
+ @VisibleForTesting
+ QuotaLimiter(Clock clock, Duration windowSize, int maxPermits) {
+ mClock = clock;
+ mWindowSize = windowSize;
+ mMaxPermits = maxPermits;
+ }
+
+ public QuotaLimiter(Duration windowSize, int maxPermits) {
+ this(Clock.SYSTEM_CLOCK, windowSize, maxPermits);
+ }
+
+ public QuotaLimiter(int maxPermitsPerDay) {
+ this(Clock.SYSTEM_CLOCK, Duration.ofDays(1), maxPermitsPerDay);
+ }
+
+ /**
+ * Acquires a permit if there is one available in the current time window.
+ *
+ * @return true if a permit was acquired.
+ */
+ boolean acquire() {
+ long nowWindow =
+ Duration.between(Instant.EPOCH, Instant.ofEpochMilli(mClock.currentTimeMillis()))
+ .dividedBy(mWindowSize);
+ if (nowWindow > mCurrentWindow) {
+ mCurrentWindow = nowWindow;
+ mPermitsGranted = 0;
+ }
+
+ if (mPermitsGranted < mMaxPermits) {
+ mPermitsGranted++;
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/RateLimiter.java b/services/core/java/com/android/server/selinux/RateLimiter.java
new file mode 100644
index 000000000000..599b8409cbc3
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/RateLimiter.java
@@ -0,0 +1,85 @@
+/*
+ * 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.selinux;
+
+import android.os.SystemClock;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+/**
+ * Rate limiter to ensure Atoms are pushed only within the allowed QPS window. This class is not
+ * thread-safe.
+ *
+ * <p>The rate limiter is smoothed, meaning that a rate limiter allowing X permits per second (or X
+ * QPS) will grant permits at a ratio of one every 1/X seconds.
+ */
+public final class RateLimiter {
+
+ private Instant mNextPermit = Instant.EPOCH;
+
+ private final Clock mClock;
+ private final Duration mWindow;
+
+ @VisibleForTesting
+ RateLimiter(Clock clock, Duration window) {
+ mClock = clock;
+ // Truncating because the system clock does not support units smaller than milliseconds.
+ mWindow = window;
+ }
+
+ /**
+ * Create a rate limiter generating one permit every {@code window} of time, using the {@link
+ * Clock.SYSTEM_CLOCK}.
+ */
+ public RateLimiter(Duration window) {
+ this(Clock.SYSTEM_CLOCK, window);
+ }
+
+ /**
+ * Acquire a permit if allowed by the rate limiter. If not, wait until a permit becomes
+ * available.
+ */
+ public void acquire() {
+ Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+ if (mNextPermit.isAfter(now)) { // Sleep until we can acquire.
+ SystemClock.sleep(ChronoUnit.MILLIS.between(now, mNextPermit));
+ mNextPermit = mNextPermit.plus(mWindow);
+ } else {
+ mNextPermit = now.plus(mWindow);
+ }
+ }
+
+ /**
+ * Try to acquire a permit if allowed by the rate limiter. Non-blocking.
+ *
+ * @return true if a permit was acquired. Otherwise, return false.
+ */
+ public boolean tryAcquire() {
+ final Instant now = Instant.ofEpochMilli(mClock.currentTimeMillis());
+
+ if (mNextPermit.isAfter(now)) {
+ return false;
+ }
+ mNextPermit = now.plus(mWindow);
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
new file mode 100644
index 000000000000..8d8d5960038e
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogBuilder.java
@@ -0,0 +1,155 @@
+/*
+ * 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.selinux;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/** Builder for SelinuxAuditLogs. */
+class SelinuxAuditLogBuilder {
+
+ // Currently logs collection is hardcoded for the sdk_sandbox_audit.
+ private static final String SDK_SANDBOX_AUDIT = "sdk_sandbox_audit";
+ static final Matcher SCONTEXT_MATCHER =
+ Pattern.compile(
+ "u:r:(?<stype>"
+ + SDK_SANDBOX_AUDIT
+ + "):s0(:c)?(?<scategories>((,c)?\\d+)+)*")
+ .matcher("");
+
+ static final Matcher TCONTEXT_MATCHER =
+ Pattern.compile("u:object_r:(?<ttype>\\w+):s0(:c)?(?<tcategories>((,c)?\\d+)+)*")
+ .matcher("");
+
+ static final Matcher PATH_MATCHER =
+ Pattern.compile("\"(?<path>/\\w+(/\\w+)?)(/\\w+)*\"").matcher("");
+
+ private Iterator<String> mTokens;
+ private final SelinuxAuditLog mAuditLog = new SelinuxAuditLog();
+
+ void reset(String denialString) {
+ mTokens =
+ Arrays.asList(
+ Optional.ofNullable(denialString)
+ .map(s -> s.split("\\s+|="))
+ .orElse(new String[0]))
+ .iterator();
+ mAuditLog.reset();
+ }
+
+ SelinuxAuditLog build() {
+ while (mTokens.hasNext()) {
+ final String token = mTokens.next();
+
+ switch (token) {
+ case "granted":
+ mAuditLog.mGranted = true;
+ break;
+ case "denied":
+ mAuditLog.mGranted = false;
+ break;
+ case "{":
+ Stream.Builder<String> permissionsStream = Stream.builder();
+ boolean closed = false;
+ while (!closed && mTokens.hasNext()) {
+ String permission = mTokens.next();
+ if ("}".equals(permission)) {
+ closed = true;
+ } else {
+ permissionsStream.add(permission);
+ }
+ }
+ if (!closed) {
+ return null;
+ }
+ mAuditLog.mPermissions = permissionsStream.build().toArray(String[]::new);
+ break;
+ case "scontext":
+ if (!nextTokenMatches(SCONTEXT_MATCHER)) {
+ return null;
+ }
+ mAuditLog.mSType = SCONTEXT_MATCHER.group("stype");
+ mAuditLog.mSCategories = toCategories(SCONTEXT_MATCHER.group("scategories"));
+ break;
+ case "tcontext":
+ if (!nextTokenMatches(TCONTEXT_MATCHER)) {
+ return null;
+ }
+ mAuditLog.mTType = TCONTEXT_MATCHER.group("ttype");
+ mAuditLog.mTCategories = toCategories(TCONTEXT_MATCHER.group("tcategories"));
+ break;
+ case "tclass":
+ if (!mTokens.hasNext()) {
+ return null;
+ }
+ mAuditLog.mTClass = mTokens.next();
+ break;
+ case "path":
+ if (nextTokenMatches(PATH_MATCHER)) {
+ mAuditLog.mPath = PATH_MATCHER.group("path");
+ }
+ break;
+ case "permissive":
+ if (!mTokens.hasNext()) {
+ return null;
+ }
+ mAuditLog.mPermissive = "1".equals(mTokens.next());
+ break;
+ default:
+ break;
+ }
+ }
+ return mAuditLog;
+ }
+
+ boolean nextTokenMatches(Matcher matcher) {
+ return mTokens.hasNext() && matcher.reset(mTokens.next()).matches();
+ }
+
+ static int[] toCategories(String categories) {
+ return categories == null
+ ? null
+ : Arrays.stream(categories.split(",c")).mapToInt(Integer::parseInt).toArray();
+ }
+
+ static class SelinuxAuditLog {
+ boolean mGranted = false;
+ String[] mPermissions = null;
+ String mSType = null;
+ int[] mSCategories = null;
+ String mTType = null;
+ int[] mTCategories = null;
+ String mTClass = null;
+ String mPath = null;
+ boolean mPermissive = false;
+
+ private void reset() {
+ mGranted = false;
+ mPermissions = null;
+ mSType = null;
+ mSCategories = null;
+ mTType = null;
+ mTCategories = null;
+ mTClass = null;
+ mPath = null;
+ mPermissive = false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
new file mode 100644
index 000000000000..0219645bee38
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsCollector.java
@@ -0,0 +1,144 @@
+/*
+ * 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.selinux;
+
+import android.util.EventLog;
+import android.util.EventLog.Event;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Class in charge of collecting SELinux audit logs and push the SELinux atoms. */
+class SelinuxAuditLogsCollector {
+
+ private static final String TAG = "SelinuxAuditLogs";
+
+ private static final String SELINUX_PATTERN = "^.*\\bavc:\\s+(?<denial>.*)$";
+
+ @VisibleForTesting
+ static final Matcher SELINUX_MATCHER = Pattern.compile(SELINUX_PATTERN).matcher("");
+
+ private final RateLimiter mRateLimiter;
+ private final QuotaLimiter mQuotaLimiter;
+
+ @VisibleForTesting Instant mLastWrite = Instant.MIN;
+
+ final AtomicBoolean mStopRequested = new AtomicBoolean(false);
+
+ SelinuxAuditLogsCollector(RateLimiter rateLimiter, QuotaLimiter quotaLimiter) {
+ mRateLimiter = rateLimiter;
+ mQuotaLimiter = quotaLimiter;
+ }
+
+ /**
+ * Collect and push SELinux audit logs for the provided {@code tagCode}.
+ *
+ * @return true if the job was completed. If the job was interrupted, return false.
+ */
+ boolean collect(int tagCode) {
+ Queue<Event> logLines = new ArrayDeque<>();
+ Instant latestTimestamp = collectLogLines(tagCode, logLines);
+
+ boolean quotaExceeded = writeAuditLogs(logLines);
+ if (quotaExceeded) {
+ Log.w(TAG, "Too many SELinux logs in the queue, I am giving up.");
+ mLastWrite = latestTimestamp; // next run we will ignore all these logs.
+ logLines.clear();
+ }
+
+ return logLines.isEmpty();
+ }
+
+ private Instant collectLogLines(int tagCode, Queue<Event> logLines) {
+ List<Event> events = new ArrayList<>();
+ try {
+ EventLog.readEvents(new int[] {tagCode}, events);
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading event logs", e);
+ }
+
+ Instant latestTimestamp = mLastWrite;
+ for (Event event : events) {
+ Instant eventTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+ if (eventTime.isAfter(latestTimestamp)) {
+ latestTimestamp = eventTime;
+ }
+ if (eventTime.isBefore(mLastWrite)) {
+ continue;
+ }
+ Object eventData = event.getData();
+ if (!(eventData instanceof String)) {
+ continue;
+ }
+ logLines.add(event);
+ }
+ return latestTimestamp;
+ }
+
+ private boolean writeAuditLogs(Queue<Event> logLines) {
+ final SelinuxAuditLogBuilder auditLogBuilder = new SelinuxAuditLogBuilder();
+
+ while (!mStopRequested.get() && !logLines.isEmpty()) {
+ Event event = logLines.poll();
+ String logLine = (String) event.getData();
+ Instant logTime = Instant.ofEpochSecond(0, event.getTimeNanos());
+ if (!SELINUX_MATCHER.reset(logLine).matches()) {
+ continue;
+ }
+
+ auditLogBuilder.reset(SELINUX_MATCHER.group("denial"));
+ final SelinuxAuditLog auditLog = auditLogBuilder.build();
+ if (auditLog == null) {
+ continue;
+ }
+
+ if (!mQuotaLimiter.acquire()) {
+ return true;
+ }
+ mRateLimiter.acquire();
+
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ auditLog.mGranted,
+ auditLog.mPermissions,
+ auditLog.mSType,
+ auditLog.mSCategories,
+ auditLog.mTType,
+ auditLog.mTCategories,
+ auditLog.mTClass,
+ auditLog.mPath,
+ auditLog.mPermissive);
+
+ if (logTime.isAfter(mLastWrite)) {
+ mLastWrite = logTime;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
new file mode 100644
index 000000000000..8a661bcc13af
--- /dev/null
+++ b/services/core/java/com/android/server/selinux/SelinuxAuditLogsService.java
@@ -0,0 +1,132 @@
+/*
+ * 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.selinux;
+
+import static com.android.sdksandbox.flags.Flags.selinuxSdkSandboxAudit;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.EventLog;
+import android.util.Log;
+
+import java.time.Duration;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Scheduled jobs related to logging of SELinux denials and audits. The job runs daily on idle
+ * devices.
+ */
+public class SelinuxAuditLogsService extends JobService {
+
+ private static final String TAG = "SelinuxAuditLogs";
+ private static final String SELINUX_AUDIT_NAMESPACE = "SelinuxAuditLogsNamespace";
+
+ static final int AUDITD_TAG_CODE = EventLog.getTagCode("auditd");
+
+ private static final int SELINUX_AUDIT_JOB_ID = 25327386;
+ private static final JobInfo SELINUX_AUDIT_JOB =
+ new JobInfo.Builder(
+ SELINUX_AUDIT_JOB_ID,
+ new ComponentName("android", SelinuxAuditLogsService.class.getName()))
+ .setPeriodic(TimeUnit.DAYS.toMillis(1))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(true)
+ .setRequiresBatteryNotLow(true)
+ .build();
+
+ private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
+ private static final AtomicReference<Boolean> IS_RUNNING = new AtomicReference<>(false);
+
+ // Audit logging is subject to both rate and quota limiting. We can only push one atom every 10
+ // milliseconds, and no more than 50K atoms can be pushed each day.
+ private static final SelinuxAuditLogsCollector AUDIT_LOGS_COLLECTOR =
+ new SelinuxAuditLogsCollector(
+ new RateLimiter(/* window= */ Duration.ofMillis(10)),
+ new QuotaLimiter(/* maxPermitsPerDay= */ 50000));
+
+ /** Schedule jobs with the {@link JobScheduler}. */
+ public static void schedule(Context context) {
+ if (!selinuxSdkSandboxAudit()) {
+ Log.d(TAG, "SelinuxAuditLogsService not enabled");
+ return;
+ }
+
+ if (AUDITD_TAG_CODE == -1) {
+ Log.e(TAG, "auditd is not a registered tag on this system");
+ return;
+ }
+
+ if (context.getSystemService(JobScheduler.class)
+ .forNamespace(SELINUX_AUDIT_NAMESPACE)
+ .schedule(SELINUX_AUDIT_JOB)
+ == JobScheduler.RESULT_FAILURE) {
+ Log.e(TAG, "SelinuxAuditLogsService could not be started.");
+ }
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+ Log.e(TAG, "The job id does not match the expected selinux job id.");
+ return false;
+ }
+
+ AUDIT_LOGS_COLLECTOR.mStopRequested.set(false);
+ IS_RUNNING.set(true);
+ EXECUTOR_SERVICE.execute(new LogsCollectorJob(this, params));
+
+ return true; // the job is running
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (params.getJobId() != SELINUX_AUDIT_JOB_ID) {
+ return false;
+ }
+
+ AUDIT_LOGS_COLLECTOR.mStopRequested.set(true);
+ return IS_RUNNING.get();
+ }
+
+ private static class LogsCollectorJob implements Runnable {
+ private final JobService mAuditLogService;
+ private final JobParameters mParams;
+
+ LogsCollectorJob(JobService auditLogService, JobParameters params) {
+ mAuditLogService = auditLogService;
+ mParams = params;
+ }
+
+ @Override
+ public void run() {
+ IS_RUNNING.updateAndGet(
+ isRunning -> {
+ boolean done = AUDIT_LOGS_COLLECTOR.collect(AUDITD_TAG_CODE);
+ if (done) {
+ mAuditLogService.jobFinished(mParams, /* wantsReschedule= */ false);
+ }
+ return !done;
+ });
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/stats/Android.bp b/services/core/java/com/android/server/stats/Android.bp
new file mode 100644
index 000000000000..e597c3a6b6e6
--- /dev/null
+++ b/services/core/java/com/android/server/stats/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+ name: "stats_flags",
+ package: "com.android.server.stats",
+ srcs: [
+ "stats_flags.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "stats_flags_lib",
+ aconfig_declarations: "stats_flags",
+}
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
new file mode 100644
index 000000000000..0de73a5a30f6
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+import android.app.ActivityManager;
+import android.app.StatsManager;
+import android.app.usage.NetworkStatsManager;
+import android.net.NetworkStats;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Aggregates Mobile Data Usage by process state per uid
+ */
+class AggregatedMobileDataStatsPuller {
+ private static final String TAG = "AggregatedMobileDataStatsPuller";
+
+ private static final boolean DEBUG = false;
+
+ private static class UidProcState {
+
+ private final int mUid;
+ private final int mState;
+
+ UidProcState(int uid, int state) {
+ mUid = uid;
+ mState = state;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof UidProcState key)) return false;
+ return mUid == key.mUid && mState == key.mState;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUid;
+ result = 31 * result + mState;
+ return result;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getState() {
+ return mState;
+ }
+
+ }
+
+ private static class MobileDataStats {
+ private long mRxPackets = 0;
+ private long mTxPackets = 0;
+ private long mRxBytes = 0;
+ private long mTxBytes = 0;
+
+ public long getRxPackets() {
+ return mRxPackets;
+ }
+
+ public long getTxPackets() {
+ return mTxPackets;
+ }
+
+ public long getRxBytes() {
+ return mRxBytes;
+ }
+
+ public long getTxBytes() {
+ return mTxBytes;
+ }
+
+ public void addRxPackets(long rxPackets) {
+ mRxPackets += rxPackets;
+ }
+
+ public void addTxPackets(long txPackets) {
+ mTxPackets += txPackets;
+ }
+
+ public void addRxBytes(long rxBytes) {
+ mRxBytes += rxBytes;
+ }
+
+ public void addTxBytes(long txBytes) {
+ mTxBytes += txBytes;
+ }
+
+ public boolean isEmpty() {
+ return mRxPackets == 0 && mTxPackets == 0 && mRxBytes == 0 && mTxBytes == 0;
+ }
+ }
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<UidProcState, MobileDataStats> mUidStats;
+
+ private final SparseIntArray mUidPreviousState;
+
+ private NetworkStats mLastMobileUidStats = new NetworkStats(0, -1);
+
+ private final NetworkStatsManager mNetworkStatsManager;
+
+ private final Handler mMobileDataStatsHandler;
+
+ AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) {
+ mUidStats = new ArrayMap<>();
+ mUidPreviousState = new SparseIntArray();
+
+ mNetworkStatsManager = networkStatsManager;
+
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ }
+
+ HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler");
+ mMobileDataStatsHandlerThread.start();
+ mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper());
+ }
+
+ public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
+ long unusedUptime) {
+ mMobileDataStatsHandler.post(
+ () -> {
+ noteUidProcessStateImpl(uid, state);
+ });
+ }
+
+ public int pullDataBytesTransfer(List<StatsEvent> data) {
+ synchronized (mLock) {
+ return pullDataBytesTransferLocked(data);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private MobileDataStats getUidStatsForPreviousStateLocked(int uid) {
+ final int previousState = mUidPreviousState.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN);
+ if (DEBUG && previousState == ActivityManager.PROCESS_STATE_UNKNOWN) {
+ Slog.d(TAG, "getUidStatsForPreviousStateLocked() no prev state info for uid "
+ + uid + ". Tracking stats with ActivityManager.PROCESS_STATE_UNKNOWN");
+ }
+
+ final UidProcState statsKey = new UidProcState(uid, previousState);
+ MobileDataStats stats;
+ if (mUidStats.containsKey(statsKey)) {
+ stats = mUidStats.get(statsKey);
+ } else {
+ stats = new MobileDataStats();
+ mUidStats.put(statsKey, stats);
+ }
+ return stats;
+ }
+
+ private void noteUidProcessStateImpl(int uid, int state) {
+ // noteUidProcessStateLocked can be called back to back several times while
+ // the updateNetworkStatsLocked loops over several stats for multiple uids
+ // and during the first call in a batch of proc state change event it can
+ // contain info for uid with unknown previous state yet which can happen due to a few
+ // reasons:
+ // - app was just started
+ // - app was started before the ActivityManagerService
+ // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ } else {
+ Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+ }
+ mUidPreviousState.put(uid, state);
+ }
+
+ private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
+ if (DEBUG) {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
+ }
+ }
+
+ final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
+ if (isEmpty(latestStats)) {
+ if (DEBUG) {
+ Slog.w(TAG, "getMobileUidStats() failed");
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ return;
+ }
+ NetworkStats delta = latestStats.subtract(mLastMobileUidStats);
+ mLastMobileUidStats = latestStats;
+
+ if (!isEmpty(delta)) {
+ updateNetworkStatsDelta(delta);
+ } else if (DEBUG) {
+ Slog.w(TAG, "updateNetworkStats() no delta");
+ }
+ if (DEBUG) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ }
+
+ private void updateNetworkStatsDelta(NetworkStats delta) {
+ synchronized (mLock) {
+ for (NetworkStats.Entry entry : delta) {
+ if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+ continue;
+ }
+ MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+ stats.addTxBytes(entry.getTxBytes());
+ stats.addRxBytes(entry.getRxBytes());
+ stats.addTxPackets(entry.getTxPackets());
+ stats.addRxPackets(entry.getRxPackets());
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private int pullDataBytesTransferLocked(List<StatsEvent> pulledData) {
+ if (DEBUG) {
+ Slog.d(TAG, "pullDataBytesTransferLocked() start");
+ }
+ for (Map.Entry<UidProcState, MobileDataStats> uidStats : mUidStats.entrySet()) {
+ if (!uidStats.getValue().isEmpty()) {
+ MobileDataStats stats = uidStats.getValue();
+ pulledData.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE,
+ uidStats.getKey().getUid(),
+ ActivityManager.processStateAmToProto(uidStats.getKey().getState()),
+ stats.getRxBytes(),
+ stats.getRxPackets(),
+ stats.getTxBytes(),
+ stats.getTxPackets()));
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "pullDataBytesTransferLocked() done. results count " + pulledData.size());
+ }
+ if (!pulledData.isEmpty()) {
+ return StatsManager.PULL_SUCCESS;
+ }
+ return StatsManager.PULL_SKIP;
+ }
+
+ private static boolean isEmpty(NetworkStats stats) {
+ long totalRxPackets = 0;
+ long totalTxPackets = 0;
+ for (NetworkStats.Entry entry : stats) {
+ if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
+ continue;
+ }
+ totalRxPackets += entry.getRxPackets();
+ totalTxPackets += entry.getTxPackets();
+ // at least one non empty entry located
+ break;
+ }
+ final long totalPackets = totalRxPackets + totalTxPackets;
+ return totalPackets == 0;
+ }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e876241f385e..285bcc328c0c 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -59,6 +59,7 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__TELEPHONY;
import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN;
import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller;
import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines;
@@ -409,6 +410,15 @@ public class StatsPullAtomService extends SystemService {
@GuardedBy("mKeystoreLock")
private IKeystoreMetrics mIKeystoreMetrics;
+ private AggregatedMobileDataStatsPuller mAggregatedMobileDataStatsPuller = null;
+
+ /**
+ * Whether or not to enable the new puller with aggregation by process state per uid on a
+ * system server side.
+ */
+ public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER =
+ addMobileBytesTransferByProcStatePuller();
+
// Puller locks
private final Object mDataBytesTransferLock = new Object();
private final Object mBluetoothBytesTransferLock = new Object();
@@ -469,6 +479,20 @@ public class StatsPullAtomService extends SystemService {
mContext = context;
}
+ private final class StatsPullAtomServiceInternalImpl extends StatsPullAtomServiceInternal {
+
+ @Override
+ public void noteUidProcessState(int uid, int state) {
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+ && mAggregatedMobileDataStatsPuller != null) {
+ final long elapsedRealtime = SystemClock.elapsedRealtime();
+ final long uptime = SystemClock.uptimeMillis();
+ mAggregatedMobileDataStatsPuller.noteUidProcessState(uid, state, elapsedRealtime,
+ uptime);
+ }
+ }
+ }
+
private native void initializeNativePullers();
/**
@@ -486,6 +510,11 @@ public class StatsPullAtomService extends SystemService {
}
try {
switch (atomTag) {
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE:
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER
+ && mAggregatedMobileDataStatsPuller != null) {
+ return mAggregatedMobileDataStatsPuller.pullDataBytesTransfer(data);
+ }
case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
@@ -776,7 +805,10 @@ public class StatsPullAtomService extends SystemService {
@Override
public void onStart() {
- // no op
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ LocalServices.addService(StatsPullAtomServiceInternal.class,
+ new StatsPullAtomServiceInternalImpl());
+ }
}
@Override
@@ -811,6 +843,9 @@ public class StatsPullAtomService extends SystemService {
mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager);
mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class);
mNetworkStatsManager = mContext.getSystemService(NetworkStatsManager.class);
+
+ initMobileDataStatsPuller();
+
// Initialize DiskIO
mStoragedUidIoStatsReader = new StoragedUidIoStatsReader();
@@ -972,6 +1007,18 @@ public class StatsPullAtomService extends SystemService {
registerCachedAppsHighWatermarkPuller();
}
+ private void initMobileDataStatsPuller() {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = "
+ + ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER);
+ }
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ mAggregatedMobileDataStatsPuller =
+ new AggregatedMobileDataStatsPuller(mNetworkStatsManager);
+ }
+ }
+
private void initAndRegisterNetworkStatsPullers() {
if (DEBUG) {
Slog.d(TAG, "Registering NetworkStats pullers with statsd");
@@ -1013,6 +1060,9 @@ public class StatsPullAtomService extends SystemService {
registerWifiBytesTransferBackground();
registerMobileBytesTransfer();
registerMobileBytesTransferBackground();
+ if (ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER) {
+ registerMobileBytesTransferByProcState();
+ }
registerBytesTransferByTagAndMetered();
registerDataUsageBytesTransfer();
registerOemManagedBytesTransfer();
@@ -1021,6 +1071,13 @@ public class StatsPullAtomService extends SystemService {
}
}
+ private void registerMobileBytesTransferByProcState() {
+ final int tagId = FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_PROC_STATE;
+ PullAtomMetadata metadata =
+ new PullAtomMetadata.Builder().setAdditiveFields(new int[] {3, 4, 5, 6}).build();
+ mStatsManager.setPullAtomCallback(tagId, metadata, DIRECT_EXECUTOR, mStatsCallbackImpl);
+ }
+
private void initAndRegisterDeferredPullers() {
mUwbManager = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_UWB)
? mContext.getSystemService(UwbManager.class) : null;
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
new file mode 100644
index 000000000000..06adbfc65abc
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomServiceInternal.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats.pull;
+
+/**
+ * System-server internal interface to the {@link StatsPullAtomService}.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class StatsPullAtomServiceInternal {
+
+ /**
+ * @param state Process state from ActivityManager.java.
+ */
+ public abstract void noteUidProcessState(int uid, int state);
+
+}
diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig
new file mode 100644
index 000000000000..5101a6982fe1
--- /dev/null
+++ b/services/core/java/com/android/server/stats/stats_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.stats"
+
+flag {
+ name: "add_mobile_bytes_transfer_by_proc_state_puller"
+ namespace: "statsd"
+ description: "Adds mobile_bytes_transfer_by_proc_state atom with system server side aggregation"
+ bug: "309512867"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index d7b8495929a2..b6d0ca19d484 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -2390,6 +2390,33 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void sendCertificate(IBinder sessionToken, String host, int port,
+ Bundle certBundle, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendCertificate(host=%s port=%d cert=%s)", host, port,
+ certBundle);
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendCertificate");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).sendCertificate(host, port, certBundle);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendCertificate", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void notifyError(IBinder sessionToken, String errMsg, Bundle params, int userId) {
if (DEBUG) {
Slogf.d(TAG, "notifyError(errMsg=%s)", errMsg);
@@ -4125,6 +4152,24 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void onRequestCertificate(String host, int port) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestCertificate");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestCertificate(host, port, mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestCertificate", e);
+ }
+ }
+ }
+
+
+ @Override
public void onAdRequest(AdRequest request) {
synchronized (mLock) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
index 19fd9a90518d..9e1b5d238d48 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDisplayHelper.java
@@ -96,7 +96,8 @@ class WallpaperDisplayHelper {
}
if (populateOrientationPairs) {
int orientation = WallpaperManager.getOrientation(displaySize);
- float newSurface = displaySize.x * displaySize.y * metric.getDensity();
+ float newSurface = displaySize.x * displaySize.y
+ / (metric.getDensity() * metric.getDensity());
if (surface <= 0) {
surface = newSurface;
firstOrientation = orientation;
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index 106be5f124a0..4cc2c025575e 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -48,6 +48,7 @@ import com.android.server.pm.KnownPackages;
import java.io.FileDescriptor;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Consumer;
/**
* System service for managing sensing {@link AmbientContextEvent}s on Wearables.
@@ -191,9 +192,23 @@ public class WearableSensingManagerService extends
}
}
+ private void callPerUserServiceIfExist(
+ Consumer<WearableSensingManagerPerUserService> serviceConsumer,
+ RemoteCallback statusCallback) {
+ int userId = UserHandle.getCallingUserId();
+ synchronized (mLock) {
+ WearableSensingManagerPerUserService service = getServiceForUserLocked(userId);
+ if (service == null) {
+ Slog.w(TAG, "Service not available for userId " + userId);
+ WearableSensingManagerPerUserService.notifyStatusCallback(statusCallback,
+ WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
+ return;
+ }
+ serviceConsumer.accept(service);
+ }
+ }
+
private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
- final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
- UserHandle.getCallingUserId());
@Override
public void provideDataStream(
@@ -210,7 +225,9 @@ public class WearableSensingManagerService extends
WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
- mService.onProvideDataStream(parcelFileDescriptor, callback);
+ callPerUserServiceIfExist(
+ service -> service.onProvideDataStream(parcelFileDescriptor, callback),
+ callback);
}
@Override
@@ -229,7 +246,9 @@ public class WearableSensingManagerService extends
WearableSensingManager.STATUS_SERVICE_UNAVAILABLE);
return;
}
- mService.onProvidedData(data, sharedMemory, callback);
+ callPerUserServiceIfExist(
+ service -> service.onProvidedData(data, sharedMemory, callback),
+ callback);
}
@Override
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2d584c428d4c..f2d9bf810b53 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -462,17 +462,16 @@ final class AccessibilityController {
}
}
- // TODO(b/318327737): Remove parameter 't' when removing flag DRAW_IN_WM_LOCK.
- void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
+ void drawMagnifiedRegionBorderIfNeeded(int displayId) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(
TAG + ".drawMagnifiedRegionBorderIfNeeded",
FLAGS_MAGNIFICATION_CALLBACK,
- "displayId=" + displayId + "; transaction={" + t + "}");
+ "displayId=" + displayId);
}
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.drawMagnifiedRegionBorderIfNeeded(t);
+ displayMagnifier.drawMagnifiedRegionBorderIfNeeded();
}
// Not relevant for the window observer.
}
@@ -870,12 +869,12 @@ final class AccessibilityController {
.sendToTarget();
}
- void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
+ void drawMagnifiedRegionBorderIfNeeded() {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
- FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}");
+ FLAGS_MAGNIFICATION_CALLBACK);
}
- mMagnifedViewport.drawWindowIfNeeded(t);
+ mMagnifedViewport.drawWindowIfNeeded();
}
void dump(PrintWriter pw, String prefix) {
@@ -1121,14 +1120,6 @@ final class AccessibilityController {
}
void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
- if (ViewportWindow.DRAW_IN_WM_LOCK) {
- if (shown) {
- mFullRedrawNeeded = true;
- mOldMagnificationRegion.set(0, 0, 0, 0);
- }
- mWindow.setShown(shown, animate);
- return;
- }
if (mWindow.setShown(shown, animate)) {
mFullRedrawNeeded = true;
// Clear the old region, so recomputeBounds will refresh the current region.
@@ -1151,12 +1142,8 @@ final class AccessibilityController {
return mMagnificationSpec;
}
- void drawWindowIfNeeded(SurfaceControl.Transaction t) {
+ void drawWindowIfNeeded() {
recomputeBounds();
- if (ViewportWindow.DRAW_IN_WM_LOCK) {
- mWindow.drawOrRemoveIfNeeded(t);
- return;
- }
mWindow.postDrawIfNeeded();
}
@@ -1187,8 +1174,6 @@ final class AccessibilityController {
private final class ViewportWindow implements Runnable {
private static final String SURFACE_TITLE = "Magnification Overlay";
- // TODO(b/318327737): Remove if it is stable.
- static final boolean DRAW_IN_WM_LOCK = !Flags.drawMagnifierBorderOutsideWmlock();
private final Region mBounds = new Region();
private final Rect mDirtyRect = new Rect();
@@ -1328,14 +1313,14 @@ final class AccessibilityController {
@Override
public void run() {
- drawOrRemoveIfNeeded(mTransaction);
+ drawOrRemoveIfNeeded();
}
/**
* This method must only be called by animation handler directly to make sure
* thread safe and there is no lock held outside.
*/
- private void drawOrRemoveIfNeeded(SurfaceControl.Transaction t) {
+ private void drawOrRemoveIfNeeded() {
// Drawing variables (alpha, dirty rect, and bounds) access is synchronized
// using WindowManagerGlobalLock. Grab copies of these values before
// drawing on the canvas so that drawing can be performed outside of the lock.
@@ -1343,7 +1328,7 @@ final class AccessibilityController {
Rect drawingRect = null;
Region drawingBounds = null;
synchronized (mService.mGlobalLock) {
- if (!DRAW_IN_WM_LOCK && mBlastBufferQueue.mNativeObject == 0) {
+ if (mBlastBufferQueue.mNativeObject == 0) {
// Complete removal since releaseSurface has been called.
if (mSurface.isValid()) {
mTransaction.remove(mSurfaceControl).apply();
@@ -1388,16 +1373,8 @@ final class AccessibilityController {
mPaint.setAlpha(alpha);
canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
mSurface.unlockCanvasAndPost(canvas);
- if (DRAW_IN_WM_LOCK) {
- t.show(mSurfaceControl);
- return;
- }
showSurface = true;
} else {
- if (DRAW_IN_WM_LOCK) {
- t.hide(mSurfaceControl);
- return;
- }
showSurface = false;
}
@@ -1413,11 +1390,6 @@ final class AccessibilityController {
@GuardedBy("mService.mGlobalLock")
void releaseSurface() {
mBlastBufferQueue.destroy();
- if (DRAW_IN_WM_LOCK) {
- mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
- mSurface.release();
- return;
- }
// Post to perform cleanup on the thread which handles mSurface.
mService.mAnimationHandler.post(this);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 036f7b6841c2..03d55d9edfda 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,6 +53,7 @@ import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
+import static android.content.Context.CONTEXT_RESTRICTED;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_HOME;
import static android.content.Intent.CATEGORY_LAUNCHER;
@@ -141,6 +142,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SWITCH;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
@@ -991,6 +993,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private CustomAppTransition mCustomOpenTransition;
private CustomAppTransition mCustomCloseTransition;
+ /** Non-zero to pause dispatching configuration changes to the client. */
+ int mPauseConfigurationDispatchCount = 0;
+
private final Runnable mPauseTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -2227,7 +2232,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mOptInOnBackInvoked = WindowOnBackInvokedDispatcher
.isOnBackInvokedCallbackEnabled(info, info.applicationInfo,
- () -> ent != null ? ent.array : null, false);
+ () -> {
+ Context appContext = null;
+ try {
+ appContext = mAtmService.mContext.createPackageContextAsUser(
+ info.packageName, CONTEXT_RESTRICTED,
+ UserHandle.of(mUserId));
+ appContext.setTheme(theme);
+ } catch (PackageManager.NameNotFoundException ignore) {
+ }
+ return appContext;
+ });
}
/**
@@ -2631,10 +2646,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (snapshot == null) {
return false;
}
- if (!snapshot.getTopActivityComponent().equals(mActivityComponent)) {
- // Obsoleted snapshot.
- return false;
- }
+ return isSnapshotComponentCompatible(snapshot) && isSnapshotOrientationCompatible(snapshot);
+ }
+
+ /**
+ * Returns {@code true} if the top activity component of task snapshot equals to this activity.
+ */
+ boolean isSnapshotComponentCompatible(@NonNull TaskSnapshot snapshot) {
+ return snapshot.getTopActivityComponent().equals(mActivityComponent);
+ }
+
+ /**
+ * Returns {@code true} if the orientation of task snapshot is compatible with this activity.
+ */
+ boolean isSnapshotOrientationCompatible(@NonNull TaskSnapshot snapshot) {
final int rotation = mDisplayContent.rotationForActivityInDifferentOrientation(this);
final int currentRotation = task.getWindowConfiguration().getRotation();
final int targetRotation = rotation != ROTATION_UNDEFINED
@@ -9276,6 +9301,59 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ @Override
+ void dispatchConfigurationToChild(WindowState child, Configuration config) {
+ if (isConfigurationDispatchPaused()) {
+ return;
+ }
+ super.dispatchConfigurationToChild(child, config);
+ }
+
+ /**
+ * Pauses dispatch of configuration changes to the client. This includes any
+ * configuration-triggered lifecycle changes, WindowState configs, and surface changes. If
+ * a lifecycle change comes from another source (eg. stop), it will still run but will use the
+ * paused configuration.
+ *
+ * The main way this works is by blocking calls to {@link #updateReportedConfigurationAndSend}.
+ * That method is responsible for evaluating whether the activity needs to be relaunched and
+ * sending configurations.
+ */
+ void pauseConfigurationDispatch() {
+ ++mPauseConfigurationDispatchCount;
+ if (mPauseConfigurationDispatchCount == 1) {
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Pausing configuration dispatch for "
+ + " %s", this);
+ }
+ }
+
+ /** @return `true` if configuration actually changed. */
+ boolean resumeConfigurationDispatch() {
+ --mPauseConfigurationDispatchCount;
+ if (mPauseConfigurationDispatchCount > 0) {
+ return false;
+ }
+ ProtoLog.v(WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Resuming configuration dispatch for %s", this);
+ if (mPauseConfigurationDispatchCount < 0) {
+ Slog.wtf(TAG, "Trying to resume non-paused configuration dispatch");
+ mPauseConfigurationDispatchCount = 0;
+ return false;
+ }
+ if (mLastReportedDisplayId == getDisplayId()
+ && getConfiguration().equals(mLastReportedConfiguration.getMergedConfiguration())) {
+ return false;
+ }
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ dispatchConfigurationToChild(getChildAt(i), getConfiguration());
+ }
+ updateReportedConfigurationAndSend();
+ return true;
+ }
+
+ boolean isConfigurationDispatchPaused() {
+ return mPauseConfigurationDispatchCount > 0;
+ }
+
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds) {
return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
@@ -9525,6 +9603,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return true;
}
+ if (isConfigurationDispatchPaused()) {
+ return true;
+ }
+
+ return updateReportedConfigurationAndSend();
+ }
+
+ boolean updateReportedConfigurationAndSend() {
+ if (isConfigurationDispatchPaused()) {
+ Slog.wtf(TAG, "trying to update reported(client) config while dispatch is paused");
+ }
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
+ "configuration: %s", this);
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index a4d15e07a3ed..83ccbdc1a4d1 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -103,10 +103,6 @@ class BackNavigationController {
static final boolean sPredictBackEnable =
SystemProperties.getBoolean("persist.wm.debug.predictive_back", true);
- static boolean isScreenshotEnabled() {
- return SystemProperties.getInt("persist.wm.debug.predictive_back_screenshot", 0) != 0;
- }
-
// Notify focus window changed
void onFocusChanged(WindowState newFocus) {
mNavigationMonitor.onFocusWindowChanged(newFocus);
@@ -310,9 +306,11 @@ class BackNavigationController {
// keyguard locked and activities are unable to show when locked.
backType = BackNavigationInfo.TYPE_CALLBACK;
}
+ } else if (currentTask.mAtmService.getLockTaskController().isTaskLocked(currentTask)) {
+ // Do not predict if current task is in task locked.
+ backType = BackNavigationInfo.TYPE_CALLBACK;
} else {
- // TODO(208789724): Create single source of truth for this, maybe in
- // RootWindowContainer
+ // Check back-to-home or cross-task
prevTask = currentTask.mRootWindowContainer.getTask(t -> {
if (t.showToCurrentUser() && !t.mChildren.isEmpty()) {
final ActivityRecord ar = t.getTopNonFinishingActivity();
@@ -958,6 +956,18 @@ class BackNavigationController {
return;
}
+ // Start fixed rotation for previous activity before create animation.
+ if (openingActivities.length == 1) {
+ final ActivityRecord next = openingActivities[0];
+ final DisplayContent dc = next.mDisplayContent;
+ dc.rotateInDifferentOrientationIfNeeded(next);
+ if (next.hasFixedRotationTransform()) {
+ // Set the record so we can recognize it to continue to update display
+ // orientation if the previous activity becomes the top later.
+ dc.setFixedRotationLaunchingApp(next,
+ next.getWindowConfiguration().getRotation());
+ }
+ }
mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open);
if (!mOpenAnimAdaptor.isValid()) {
Slog.w(TAG, "compose animations fail, skip");
@@ -1623,16 +1633,6 @@ class BackNavigationController {
}
activity.mLaunchTaskBehind = true;
- // Handle fixed rotation launching app.
- final DisplayContent dc = activity.mDisplayContent;
- dc.rotateInDifferentOrientationIfNeeded(activity);
- if (activity.hasFixedRotationTransform()) {
- // Set the record so we can recognize it to continue to update display
- // orientation if the previous activity becomes the top later.
- dc.setFixedRotationLaunchingApp(activity,
- activity.getWindowConfiguration().getRotation());
- }
-
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
activity.mTaskSupervisor.mStoppingActivities.remove(activity);
@@ -1700,21 +1700,38 @@ class BackNavigationController {
static TaskSnapshot getSnapshot(@NonNull WindowContainer w,
ActivityRecord[] visibleOpenActivities) {
+ TaskSnapshot snapshot = null;
if (w.asTask() != null) {
final Task task = w.asTask();
- return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
+ snapshot = task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
task.mTaskId, task.mUserId, false /* restoreFromDisk */,
false /* isLowResolution */);
- }
-
- if (w.asActivityRecord() != null) {
+ } else if (w.asActivityRecord() != null) {
final ActivityRecord ar = w.asActivityRecord();
- return ar.mWmService.mSnapshotController.mActivitySnapshotController
+ snapshot = ar.mWmService.mSnapshotController.mActivitySnapshotController
.getSnapshot(visibleOpenActivities);
}
- return null;
+
+ return isSnapshotCompatible(snapshot, visibleOpenActivities) ? snapshot : null;
}
+ static boolean isSnapshotCompatible(@NonNull TaskSnapshot snapshot,
+ @NonNull ActivityRecord[] visibleOpenActivities) {
+ if (snapshot == null) {
+ return false;
+ }
+ boolean oneComponentMatch = false;
+ for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+ final ActivityRecord ar = visibleOpenActivities[i];
+ if (!ar.isSnapshotOrientationCompatible(snapshot)) {
+ return false;
+ }
+ oneComponentMatch |= ar.isSnapshotComponentCompatible(snapshot);
+ }
+ return oneComponentMatch;
+ }
+
+
void setWindowManager(WindowManagerService wm) {
mWindowManagerService = wm;
mAnimationHandler = new AnimationHandler(wm);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 9ac4a5c4ad5c..5423c6636786 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -717,31 +717,6 @@ public class BackgroundActivityStartController {
boolean callerCanAllow = resultForCaller.allows() && !state.callerExplicitOptOut();
boolean realCallerCanAllow = resultForRealCaller.allows()
&& !state.realCallerExplicitOptOut();
- if (callerCanAllow && realCallerCanAllow) {
- // Both caller and real caller allow with system defined behavior
- if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
- // Will be allowed even with BAL hardening.
- if (DEBUG_ACTIVITY_STARTS) {
- Slog.d(TAG, "Activity start allowed by caller. "
- + state.dump());
- }
- return allowBasedOnCaller(state);
- }
- if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
- Slog.wtf(TAG,
- "With Android 15 BAL hardening this activity start may be blocked"
- + " if the PI creator upgrades target_sdk to 35+"
- + " AND the PI sender upgrades target_sdk to 34+! "
- + state.dump());
- showBalRiskToast();
- return allowBasedOnCaller(state);
- }
- Slog.wtf(TAG,
- "Without Android 15 BAL hardening this activity start would be allowed"
- + " (missing opt in by PI creator or sender)! "
- + state.dump());
- return abortLaunch(state);
- }
if (callerCanAllow) {
// Allowed before V by creator
if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
@@ -753,35 +728,29 @@ public class BackgroundActivityStartController {
return allowBasedOnCaller(state);
}
if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
- Slog.wtf(TAG,
- "With Android 15 BAL hardening this activity start may be blocked"
+ Slog.wtf(TAG, "With Android 15 BAL hardening this activity start may be blocked"
+ " if the PI creator upgrades target_sdk to 35+! "
+ " (missing opt in by PI creator)! "
+ state.dump());
showBalRiskToast();
return allowBasedOnCaller(state);
}
- Slog.wtf(TAG,
- "Without Android 15 BAL hardening this activity start would be allowed"
- + " (missing opt in by PI creator)! "
- + state.dump());
- return abortLaunch(state);
}
if (realCallerCanAllow) {
// Allowed before U by sender
if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts()) {
- Slog.wtf(TAG,
- "With Android 14 BAL hardening this activity start will be blocked"
+ Slog.wtf(TAG, "With Android 14 BAL hardening this activity start will be blocked"
+ " if the PI sender upgrades target_sdk to 34+! "
+ " (missing opt in by PI sender)! "
+ state.dump());
showBalRiskToast();
return allowBasedOnRealCaller(state);
}
- Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
- + " (missing opt in by PI sender)! "
- + state.dump());
- return abortLaunch(state);
+ }
+ // caller or real caller could start the activity, but would need to explicitly opt in
+ if (callerCanAllow || realCallerCanAllow) {
+ Slog.wtf(TAG, "Without BAL hardening this activity start would be allowed "
+ + state.dump());
}
// neither the caller not the realCaller can allow or have explicitly opted out
return abortLaunch(state);
@@ -1028,7 +997,7 @@ public class BackgroundActivityStartController {
BalVerdict balAllowedForUid = proc.areBackgroundActivityStartsAllowed(
state.mAppSwitchState);
if (balAllowedForUid.allows()) {
- return balAllowedForCaller.withProcessInfo("process", proc);
+ return balAllowedForUid.withProcessInfo("process", proc);
}
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e7ecf520425d..5dcd335baf2d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -160,6 +160,7 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELD
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
+import static com.android.window.flags.Flags.deferDisplayUpdates;
import static com.android.window.flags.Flags.explicitRefreshRateHints;
import android.annotation.IntDef;
@@ -174,7 +175,6 @@ import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
import android.graphics.ColorSpace;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -246,7 +246,6 @@ import android.view.inputmethod.ImeTracker;
import android.window.DisplayWindowPolicyController;
import android.window.IDisplayAreaOrganizer;
import android.window.ScreenCapture;
-import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import android.window.SystemPerformanceHinter;
import android.window.TransitionRequestInfo;
@@ -276,7 +275,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import static com.android.window.flags.Flags.deferDisplayUpdates;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -5207,10 +5205,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * Takes a snapshot of the display. In landscape mode this grabs the whole screen.
- * In portrait mode, it grabs the full screenshot.
+ * Creates a LayerCaptureArgs object to represent the entire DisplayContent
*/
- Bitmap screenshotDisplayLocked() {
+ ScreenCapture.LayerCaptureArgs getLayerCaptureArgs() {
if (!mWmService.mPolicy.isScreenOn()) {
if (DEBUG_SCREENSHOT) {
Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
@@ -5218,24 +5215,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return null;
}
- SynchronousScreenCaptureListener syncScreenCapture =
- ScreenCapture.createSyncCaptureListener();
-
getBounds(mTmpRect);
mTmpRect.offsetTo(0, 0);
- ScreenCapture.LayerCaptureArgs args =
- new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
- .setSourceCrop(mTmpRect).build();
-
- ScreenCapture.captureLayers(args, syncScreenCapture);
-
- final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
- syncScreenCapture.getBuffer();
- final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
- if (bitmap == null) {
- Slog.w(TAG_WM, "Failed to take screenshot");
- }
- return bitmap;
+ return new ScreenCapture.LayerCaptureArgs.Builder(getSurfaceControl())
+ .setSourceCrop(mTmpRect).build();
}
@Override
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 7f785af1671e..a1799b473c95 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -92,35 +92,39 @@ public class DisplayFrames {
mRotation = rotation;
mWidth = w;
mHeight = h;
- final Rect unrestricted = mUnrestricted;
- unrestricted.set(0, 0, w, h);
- state.setDisplayFrame(unrestricted);
+ final Rect u = mUnrestricted;
+ u.set(0, 0, w, h);
+ state.setDisplayFrame(u);
state.setDisplayCutout(displayCutout);
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
state.setDisplayShape(displayShape);
state.getDisplayCutoutSafe(safe);
- if (safe.left > unrestricted.left) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame(
- unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
+ if (safe.left > u.left) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout())
+ .setFrame(u.left, u.top, safe.left, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_LEFT);
}
- if (safe.top > unrestricted.top) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame(
- unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
+ if (safe.top > u.top) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout())
+ .setFrame(u.left, u.top, u.right, safe.top)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_TOP);
}
- if (safe.right < unrestricted.right) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame(
- safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
+ if (safe.right < u.right) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout())
+ .setFrame(safe.right, u.top, u.right, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_RIGHT);
}
- if (safe.bottom < unrestricted.bottom) {
- state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame(
- unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
+ if (safe.bottom < u.bottom) {
+ state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout())
+ .setFrame(u.left, safe.bottom, u.right, u.bottom)
+ .updateSideHint(u);
} else {
state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 9d5ddf3bf264..d9dda4aeb96a 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -62,6 +62,8 @@ import java.util.function.Consumer;
*/
class InsetsSourceProvider {
+ private static final Rect EMPTY_RECT = new Rect();
+
protected final DisplayContent mDisplayContent;
protected final @NonNull InsetsSource mSource;
protected WindowContainer mWindowContainer;
@@ -286,12 +288,15 @@ class InsetsSourceProvider {
private void updateSourceFrameForServerVisibility() {
// Make sure we set the valid source frame only when server visible is true, because the
- // frame may not yet determined that server side doesn't think the window is ready to
+ // frame may not yet be determined that server side doesn't think the window is ready to
// visible. (i.e. No surface, pending insets that were given during layout, etc..)
- if (mServerVisible) {
- mSource.setFrame(mSourceFrame);
- } else {
- mSource.setFrame(0, 0, 0, 0);
+ final Rect frame = mServerVisible ? mSourceFrame : EMPTY_RECT;
+ if (mSource.getFrame().equals(frame)) {
+ return;
+ }
+ mSource.setFrame(frame);
+ if (mWindowContainer != null) {
+ mSource.updateSideHint(mWindowContainer.getBounds());
}
}
@@ -631,7 +636,7 @@ class InsetsSourceProvider {
}
pw.print(prefix);
pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
- pw.print("mHasPendingPosition="); pw.print(mHasPendingPosition);
+ pw.print(" mHasPendingPosition="); pw.print(mHasPendingPosition);
pw.println();
if (mWindowContainer != null) {
pw.print(prefix + "mWindowContainer=");
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f2796895d639..0e2d3d151db0 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1424,7 +1424,7 @@ final class LetterboxUiController {
@VisibleForTesting
boolean shouldShowLetterboxUi(WindowState mainWindow) {
- if (mIsRelaunchingAfterRequestedOrientationChanged || !isSurfaceReadyToShow(mainWindow)) {
+ if (mIsRelaunchingAfterRequestedOrientationChanged) {
return mLastShouldShowLetterboxUi;
}
@@ -1442,13 +1442,6 @@ final class LetterboxUiController {
}
@VisibleForTesting
- boolean isSurfaceReadyToShow(WindowState mainWindow) {
- return mainWindow.isDrawn() // Regular case
- // Waiting for relayoutWindow to call preserveSurface
- || mainWindow.isDragResizeChanged();
- }
-
- @VisibleForTesting
boolean isSurfaceVisible(WindowState mainWindow) {
return mainWindow.isOnScreen() && (mActivityRecord.isVisible()
|| mActivityRecord.isVisibleRequested());
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 10405ec7cd88..e027eb63f1d5 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1135,16 +1135,17 @@ class RecentTasks {
if (!mFreezeTaskListReordering) {
// Simple case: this is not an affiliated task, so we just move it to the
// front unless overridden by the provided activity options
+ int indexToAdd = findIndexToAdd(task);
mTasks.remove(taskIndex);
- mTasks.add(0, task);
+ mTasks.add(indexToAdd, task);
if (taskIndex != 0) {
// Only notify when position changes
mTaskNotificationController.notifyTaskListUpdated();
}
if (DEBUG_RECENTS) {
- Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
- + " from " + taskIndex);
+ Slog.d(TAG_RECENTS, "addRecent: moving " + task + " to index "
+ + indexToAdd + " from " + taskIndex);
}
}
notifyTaskPersisterLocked(task, false);
@@ -1231,6 +1232,37 @@ class RecentTasks {
notifyTaskPersisterLocked(task, false /* flush */);
}
+ // Looks for a new index to move the recent Task. Note that the recent Task should not be
+ // placed higher than another recent Task that has higher hierarchical z-ordering.
+ private int findIndexToAdd(Task task) {
+ int indexToAdd = 0;
+ for (int i = 0; i < mTasks.size(); i++) {
+ final Task otherTask = mTasks.get(i);
+ if (task == otherTask) {
+ break;
+ }
+
+ if (!otherTask.isAttached()) {
+ // Stop searching if not attached.
+ break;
+ }
+
+ if (otherTask.inPinnedWindowingMode()) {
+ // Skip pip task without increasing index since pip is always on screen.
+ continue;
+ }
+
+ // Stop searching if the task has higher z-ordering, or increase the index and
+ // continue the search.
+ if (task.compareTo(otherTask) > 0) {
+ break;
+ }
+
+ indexToAdd = i + 1;
+ }
+ return indexToAdd;
+ }
+
/**
* Add the task to the bottom if possible.
*/
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index f10a733040ed..083872a03edd 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -669,7 +669,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+ public void sendWallpaperCommand(IBinder window, String action, int x, int y,
int z, Bundle extras, boolean sync) {
synchronized (mService.mGlobalLock) {
final long ident = Binder.clearCallingIdentity();
@@ -680,10 +680,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (mCanAlwaysUpdateWallpaper
|| windowState == wallpaperController.getWallpaperTarget()
|| windowState == wallpaperController.getPrevWallpaperTarget()) {
- return wallpaperController.sendWindowWallpaperCommandUnchecked(
+ wallpaperController.sendWindowWallpaperCommandUnchecked(
windowState, action, x, y, z, extras, sync);
}
- return null;
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6371bb45ade9..b2b547e7d9e5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -45,7 +45,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
-import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
@@ -89,7 +88,6 @@ import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
-import android.hardware.HardwareBuffer;
import android.os.IBinder;
import android.os.UserHandle;
import android.util.DisplayMetrics;
@@ -99,7 +97,6 @@ import android.view.DisplayInfo;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.ITaskFragmentOrganizer;
-import android.window.ScreenCapture;
import android.window.TaskFragmentAnimationParams;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizerToken;
@@ -113,7 +110,6 @@ import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -369,6 +365,15 @@ class TaskFragment extends WindowContainer<WindowContainer> {
*/
private boolean mMoveToBottomIfClearWhenLaunch;
+ /**
+ * If {@code true}, transitions are allowed even if this TaskFragment is empty. If
+ * {@code false}, transitions will wait until this TaskFragment becomes non-empty or other
+ * conditions are met. Default to {@code false}.
+ *
+ * @see #isReadyToTransit
+ */
+ private boolean mAllowTransitionWhenEmpty;
+
/** When set, will force the task to report as invisible. */
static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
@@ -394,10 +399,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
/** For calculating app bounds, i.e. the area without the nav bar and display cutout. */
private final Rect mTmpNonDecorBounds = new Rect();
- //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
- // implemented
- HashMap<String, ScreenCapture.ScreenshotHardwareBuffer> mBackScreenshots = new HashMap<>();
-
private final EnsureActivitiesVisibleHelper mEnsureActivitiesVisibleHelper =
new EnsureActivitiesVisibleHelper(this);
@@ -509,6 +510,19 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mIsolatedNav = isolatedNav;
}
+ /**
+ * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true},
+ * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions
+ * will wait until the TaskFragment becomes non-empty or other conditions are met. Default
+ * to {@code false}.
+ */
+ void setAllowTransitionWhenEmpty(boolean allowTransitionWhenEmpty) {
+ if (!isEmbedded()) {
+ return;
+ }
+ mAllowTransitionWhenEmpty = allowTransitionWhenEmpty;
+ }
+
/** @see #mIsolatedNav */
boolean isIsolatedNav() {
return isEmbedded() && mIsolatedNav;
@@ -2070,17 +2084,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
super.addChild(child, index);
if (isAddingActivity && task != null) {
- // TODO(b/207481538): temporary per-activity screenshoting
- if (r != null && BackNavigationController.isScreenshotEnabled()) {
- ProtoLog.v(WM_DEBUG_BACK_PREVIEW, "Screenshotting Activity %s",
- r.mActivityComponent.flattenToString());
- Rect outBounds = r.getBounds();
- ScreenCapture.ScreenshotHardwareBuffer backBuffer = ScreenCapture.captureLayers(
- r.mSurfaceControl,
- new Rect(0, 0, outBounds.width(), outBounds.height()),
- 1f);
- mBackScreenshots.put(r.mActivityComponent.flattenToString(), backBuffer);
- }
addingActivity.inHistory = true;
task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity);
}
@@ -2827,8 +2830,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
// We don't want to start the transition if the organized TaskFragment is empty, unless
- // it is requested to be removed.
- if (getTopNonFinishingActivity() != null || mIsRemovalRequested) {
+ // it is requested to be removed or the mAllowTransitionWhenEmpty flag is true.
+ if (getTopNonFinishingActivity() != null || mIsRemovalRequested
+ || mAllowTransitionWhenEmpty) {
return true;
}
// Organizer shouldn't change embedded TaskFragment in PiP.
@@ -2882,19 +2886,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return !mCreatedByOrganizer || mIsRemovalRequested;
}
- @Nullable
- HardwareBuffer getSnapshotForActivityRecord(@Nullable ActivityRecord r) {
- if (!BackNavigationController.isScreenshotEnabled()) {
- return null;
- }
- if (r != null && r.mActivityComponent != null) {
- ScreenCapture.ScreenshotHardwareBuffer backBuffer =
- mBackScreenshots.get(r.mActivityComponent.flattenToString());
- return backBuffer != null ? backBuffer.getHardwareBuffer() : null;
- }
- return null;
- }
-
@Override
void removeChild(WindowContainer child) {
removeChild(child, true /* removeSelfIfPossible */);
@@ -2903,13 +2894,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
void removeChild(WindowContainer child, boolean removeSelfIfPossible) {
super.removeChild(child);
final ActivityRecord r = child.asActivityRecord();
- if (BackNavigationController.isScreenshotEnabled()) {
- //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is
- // implemented
- if (r != null) {
- mBackScreenshots.remove(r.mActivityComponent.flattenToString());
- }
- }
final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(r);
if (hostProcess != null) {
hostProcess.removeEmbeddedActivity(r);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 59e3350d5c13..d7b4a399514d 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -496,6 +496,9 @@ class TransitionController {
if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) {
return true;
}
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isInTransientHide(task)) return true;
+ }
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
if (mPlayingTransitions.get(i).isInTransientHide(task)) return true;
}
@@ -506,6 +509,9 @@ class TransitionController {
if (mCollectingTransition != null && mCollectingTransition.isTransientVisible(task)) {
return true;
}
+ for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
+ if (mWaitingTransitions.get(i).isTransientVisible(task)) return true;
+ }
for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
if (mPlayingTransitions.get(i).isTransientVisible(task)) return true;
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 0fc62a758c5e..6949a874b533 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -279,13 +279,14 @@ class WallpaperController {
return null;
}
Point largestDisplaySize = new Point();
+ float largestWidth = 0;
List<DisplayInfo> possibleDisplayInfo =
mService.getPossibleDisplayInfoLocked(DEFAULT_DISPLAY);
for (int i = 0; i < possibleDisplayInfo.size(); i++) {
DisplayInfo displayInfo = possibleDisplayInfo.get(i);
- if (displayInfo.type == Display.TYPE_INTERNAL
- && Math.max(displayInfo.logicalWidth, displayInfo.logicalHeight)
- > Math.max(largestDisplaySize.x, largestDisplaySize.y)) {
+ float width = (float) displayInfo.logicalWidth / displayInfo.physicalXDpi;
+ if (displayInfo.type == Display.TYPE_INTERNAL && width > largestWidth) {
+ largestWidth = width;
largestDisplaySize.set(displayInfo.logicalWidth,
displayInfo.logicalHeight);
}
@@ -641,11 +642,10 @@ class WallpaperController {
}
}
- Bundle sendWindowWallpaperCommandUnchecked(
+ void sendWindowWallpaperCommandUnchecked(
WindowState window, String action, int x, int y, int z,
Bundle extras, boolean sync) {
sendWindowWallpaperCommand(action, x, y, z, extras, sync);
- return null;
}
private void sendWindowWallpaperCommand(
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 750fd509e50f..b43a4540bbde 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -146,10 +146,11 @@ public class WindowAnimator {
for (int i = 0; i < numDisplays; i++) {
final DisplayContent dc = root.getChildAt(i);
- dc.checkAppWindowsReadyToShow();
+ if (!useShellTransition) {
+ dc.checkAppWindowsReadyToShow();
+ }
if (accessibilityController.hasCallbacks()) {
- accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId,
- mTransaction);
+ accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId);
}
if (dc.isAnimating(animationFlags, ANIMATION_TYPE_ALL)) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index bdea1bc40f3a..286182eedf44 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -465,7 +465,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
final InsetsSource source = new InsetsSource(id, provider.getType());
- source.setFrame(provider.getArbitraryRectangle());
+ source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds());
mLocalInsetsSources.put(id, source);
mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9650b8bc2281..426694d178af 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4087,7 +4087,7 @@ public class WindowManagerService extends IWindowManager.Stub
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
- final Bitmap bm;
+ ScreenCapture.LayerCaptureArgs captureArgs;
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(DEFAULT_DISPLAY);
if (displayContent == null) {
@@ -4095,12 +4095,30 @@ public class WindowManagerService extends IWindowManager.Stub
Slog.i(TAG_WM, "Screenshot returning null. No Display for displayId="
+ DEFAULT_DISPLAY);
}
- bm = null;
+ captureArgs = null;
} else {
- bm = displayContent.screenshotDisplayLocked();
+ captureArgs = displayContent.getLayerCaptureArgs();
}
}
+ final Bitmap bm;
+ if (captureArgs != null) {
+ ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture =
+ ScreenCapture.createSyncCaptureListener();
+
+ ScreenCapture.captureLayers(captureArgs, syncScreenCapture);
+
+ final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+ syncScreenCapture.getBuffer();
+ bm = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+ } else {
+ bm = null;
+ }
+
+ if (bm == null) {
+ Slog.w(TAG_WM, "Failed to take screenshot");
+ }
+
FgThread.getHandler().post(() -> {
try {
receiver.onHandleAssistScreenshot(bm);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 205ed977f316..4ba52e4c0fd7 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2202,6 +2202,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
final TaskFragment taskFragment = new TaskFragment(mService,
creationParams.getFragmentToken(), true /* createdByOrganizer */);
+ taskFragment.setAllowTransitionWhenEmpty(creationParams.getAllowTransitionWhenEmpty());
// Set task fragment organizer immediately, since it might have to be notified about further
// actions.
TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56f2bc3d3e3b..24e50c54aa61 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -169,6 +169,7 @@ import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.REMOVED;
import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
+import static com.android.server.wm.WindowStateProto.REQUESTED_VISIBLE_TYPES;
import static com.android.server.wm.WindowStateProto.REQUESTED_WIDTH;
import static com.android.server.wm.WindowStateProto.STACK_ID;
import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
@@ -3988,6 +3989,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate);
proto.write(HAS_COMPAT_SCALE, hasCompatScale());
proto.write(GLOBAL_SCALE, mGlobalScale);
+ proto.write(REQUESTED_VISIBLE_TYPES, mRequestedVisibleTypes);
for (Rect r : mKeepClearAreas) {
r.dumpDebug(proto, KEEP_CLEAR_AREAS);
}
@@ -5187,6 +5189,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mSurfaceControl == null) {
return;
}
+ if (mActivityRecord != null && mActivityRecord.isConfigurationDispatchPaused()) {
+ // Don't update surface-position while dispatch paused. This is calculated from
+ // the server-side activity configuration so return early.
+ return;
+ }
if ((mWmService.mWindowPlacerLocked.isLayoutDeferred() || isGoneForLayout())
&& !mSurfacePlacementNeeded) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5048cef3da1b..13e1ba785b87 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -639,9 +639,12 @@ class WindowToken extends WindowContainer<WindowState> {
@Override
void updateSurfacePosition(SurfaceControl.Transaction t) {
+ final ActivityRecord r = asActivityRecord();
+ if (r != null && r.isConfigurationDispatchPaused()) {
+ return;
+ }
super.updateSurfacePosition(t);
if (!mTransitionController.isShellTransitionsEnabled() && isFixedRotationTransforming()) {
- final ActivityRecord r = asActivityRecord();
final Task rootTask = r != null ? r.getRootTask() : null;
// Don't transform the activity in PiP because the PiP task organizer will handle it.
if (rootTask == null || !rootTask.inPinnedWindowingMode()) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 8bc41af8af62..cbc301b87295 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -306,7 +306,7 @@ public:
void setMotionClassifierEnabled(bool enabled);
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
- FloatPoint getMouseCursorPosition();
+ FloatPoint getMouseCursorPosition(int32_t displayId);
void setStylusPointerIconEnabled(bool enabled);
/* --- InputReaderPolicyInterface implementation --- */
@@ -1784,10 +1784,12 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) {
InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING);
}
-FloatPoint NativeInputManager::getMouseCursorPosition() {
+FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) {
if (ENABLE_POINTER_CHOREOGRAPHER) {
- return mInputManager->getChoreographer().getMouseCursorPosition(ADISPLAY_ID_NONE);
+ return mInputManager->getChoreographer().getMouseCursorPosition(displayId);
}
+ // To maintain the status-quo, the displayId parameter (used when PointerChoreographer is
+ // enabled) is ignored in the old pipeline.
std::scoped_lock _l(mLock);
const auto pc = mLocked.legacyPointerController.lock();
if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
@@ -2751,9 +2753,10 @@ static void nativeSetStylusButtonMotionEventsEnabled(JNIEnv* env, jobject native
im->setStylusButtonMotionEventsEnabled(enabled);
}
-static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj) {
+static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj,
+ jint displayId) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- const auto p = im->getMouseCursorPosition();
+ const auto p = im->getMouseCursorPosition(displayId);
const std::array<float, 2> arr = {{p.x, p.y}};
jfloatArray outArr = env->NewFloatArray(2);
env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data());
@@ -2775,6 +2778,15 @@ static void nativeSetAccessibilityBounceKeysThreshold(JNIEnv* env, jobject nativ
}
}
+static void nativeSetAccessibilitySlowKeysThreshold(JNIEnv* env, jobject nativeImplObj,
+ jint thresholdTimeMs) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ if (ENABLE_INPUT_FILTER_RUST) {
+ im->getInputManager()->getInputFilter().setAccessibilitySlowKeysThreshold(
+ static_cast<nsecs_t>(thresholdTimeMs) * 1000000);
+ }
+}
+
static void nativeSetAccessibilityStickyKeysEnabled(JNIEnv* env, jobject nativeImplObj,
jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2883,10 +2895,12 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
{"setStylusButtonMotionEventsEnabled", "(Z)V",
(void*)nativeSetStylusButtonMotionEventsEnabled},
- {"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
+ {"getMouseCursorPosition", "(I)[F", (void*)nativeGetMouseCursorPosition},
{"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
{"setAccessibilityBounceKeysThreshold", "(I)V",
(void*)nativeSetAccessibilityBounceKeysThreshold},
+ {"setAccessibilitySlowKeysThreshold", "(I)V",
+ (void*)nativeSetAccessibilitySlowKeysThreshold},
{"setAccessibilityStickyKeysEnabled", "(Z)V",
(void*)nativeSetAccessibilityStickyKeysEnabled},
};
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index f288103bd954..519c9bb16eed 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -71,6 +71,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_STATUS_BAR;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
@@ -484,6 +485,7 @@ import com.android.internal.widget.PasswordValidationError;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.net.module.util.ProxyUtils;
+import com.android.net.thread.flags.Flags;
import com.android.server.AlarmManagerInternal;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
@@ -13339,6 +13341,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
+ if (Flags.threadUserRestrictionEnabled()) {
+ USER_RESTRICTION_PERMISSIONS.put(
+ UserManager.DISALLOW_THREAD_NETWORK,
+ new String[]{MANAGE_DEVICE_POLICY_THREAD_NETWORK});
+ }
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 86ad49458c48..2b8bcc77281a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -203,6 +203,7 @@ import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
import com.android.server.security.rkp.RemoteProvisioningService;
+import com.android.server.selinux.SelinuxAuditLogsService;
import com.android.server.sensorprivacy.SensorPrivacyService;
import com.android.server.sensors.SensorService;
import com.android.server.signedconfig.SignedConfigService;
@@ -433,6 +434,9 @@ public final class SystemServer implements Dumpable {
private static final String ROLE_SERVICE_CLASS = "com.android.role.RoleService";
private static final String GAME_MANAGER_SERVICE_CLASS =
"com.android.server.app.GameManagerService$Lifecycle";
+ private static final String ENHANCED_CONFIRMATION_SERVICE_CLASS =
+ "com.android.ecm.EnhancedConfirmationService";
+
private static final String UWB_APEX_SERVICE_JAR_PATH =
"/apex/com.android.uwb/javalib/service-uwb.jar";
private static final String UWB_SERVICE_CLASS = "com.android.server.uwb.UwbService";
@@ -1592,6 +1596,12 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(DropBoxManagerService.class);
t.traceEnd();
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+ t.traceBegin("StartEnhancedConfirmationService");
+ mSystemServiceManager.startService(ENHANCED_CONFIRMATION_SERVICE_CLASS);
+ t.traceEnd();
+ }
+
// Grants default permissions and defines roles
t.traceBegin("StartRoleManagerService");
LocalManagerRegistry.addManager(RoleServicePlatformHelper.class,
@@ -2609,6 +2619,14 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
+ t.traceBegin("StartSelinuxAuditLogsService");
+ try {
+ SelinuxAuditLogsService.schedule(context);
+ } catch (Throwable e) {
+ reportWtf("starting SelinuxAuditLogsService", e);
+ }
+ t.traceEnd();
+
// LauncherAppsService uses ShortcutService.
t.traceBegin("StartShortcutServiceLifecycle");
mSystemServiceManager.startService(ShortcutService.Lifecycle.class);
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
index a0fb0138e5e5..3284cf19db43 100644
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
@@ -94,7 +94,9 @@ class DevicePermissionPolicy : SchemePolicy() {
isSystemUpdated: Boolean
) {
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ // The package may still be removed even if it was once notified as installed.
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
trimPermissionStates(packageState.appId)
}
}
@@ -127,7 +129,10 @@ class DevicePermissionPolicy : SchemePolicy() {
val packageState = newState.externalState.packageStates[packageName] ?: return
val androidPackage = packageState.androidPackage ?: return
val appId = packageState.appId
- val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
+ // The user may happen removed due to DeletePackageHelper.removeUnusedPackagesLPw() calling
+ // deletePackageX() asynchronously.
+ val userState = newState.userStates[userId] ?: return
+ val devicePermissionFlags = userState.appIdDevicePermissionFlags[appId] ?: return
androidPackage.requestedPermissions.forEach { permissionName ->
val isRequestedByOtherPackages =
anyPackageInAppId(appId) {
@@ -137,7 +142,7 @@ class DevicePermissionPolicy : SchemePolicy() {
if (isRequestedByOtherPackages) {
return@forEach
}
- appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ ->
+ devicePermissionFlags.forEachIndexed { _, deviceId, _ ->
setPermissionFlags(appId, deviceId, userId, permissionName, 0)
}
}
@@ -245,6 +250,13 @@ class DevicePermissionPolicy : SchemePolicy() {
flagMask: Int,
flagValues: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+ // sometimes get race conditions between that check and the actual mutateState() call.
+ // This should rarely happen but at least we should not crash.
+ Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+ return false
+ }
val oldFlags =
newState.userStates[userId]!!
.appIdDevicePermissionFlags[appId]
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
index 30afa72e0f03..b9f1ea06aebe 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -16,13 +16,14 @@
package com.android.server.inputmethod;
import static com.android.server.inputmethod.ClientController.ClientControllerCallback;
-import static com.android.server.inputmethod.ClientController.ClientState;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,6 +54,7 @@ public final class ClientControllerTest {
private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
private static final int ANY_CALLER_UID = 1;
private static final int ANY_CALLER_PID = 1;
+ private static final String SOME_PACKAGE_NAME = "some.package";
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
@@ -81,7 +83,8 @@ public final class ClientControllerTest {
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes.
@IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testAddClient_cannotAddTheSameClientTwice() {
var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
@@ -103,7 +106,8 @@ public final class ClientControllerTest {
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes.
@IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testAddClient() throws Exception {
synchronized (ImfLock.class) {
@@ -117,7 +121,8 @@ public final class ClientControllerTest {
}
@Test
- // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes.
@IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
public void testRemoveClient() {
var callback = new TestClientControllerCallback();
@@ -137,6 +142,36 @@ public final class ClientControllerTest {
assertThat(removed).isSameInstanceAs(added);
}
+ @Test
+ // TODO(b/314150112): Enable host side mode for this test once Ravenwood is enabled for
+ // inputmethod server classes and updated to newer Mockito with static mock support (mock
+ // InputMethodUtils#checkIfPackageBelongsToUid instead of PackageManagerInternal#isSameApp)
+ @IgnoreUnderRavenwood(blockedBy = {InputMethodUtils.class})
+ public void testVerifyClientAndPackageMatch() {
+ when(mMockPackageManagerInternal.isSameApp(eq(SOME_PACKAGE_NAME), /* flags= */
+ anyLong(), eq(ANY_CALLER_UID), /* userId= */ anyInt())).thenReturn(true);
+
+ synchronized (ImfLock.class) {
+ var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+ mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, ANY_CALLER_UID,
+ ANY_CALLER_PID);
+ assertThat(
+ mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME)).isTrue();
+ }
+ }
+
+ @Test
+ public void testVerifyClientAndPackageMatch_unknownClient() {
+ synchronized (ImfLock.class) {
+ assertThrows(IllegalArgumentException.class,
+ () -> {
+ synchronized (ImfLock.class) {
+ mController.verifyClientAndPackageMatch(mClient, SOME_PACKAGE_NAME);
+ }
+ });
+ }
+ }
+
private static class TestClientControllerCallback implements ClientControllerCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 438bea458c47..1c71a6287c79 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,7 +22,6 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VI
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
-import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
new file mode 100644
index 000000000000..fcf761fb6607
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ProcessObserverTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.server.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.IProcessObserver;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify that process events are dispatched to process observers.
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class ProcessObserverTest {
+ private static final String TAG = "ProcessObserverTest";
+
+ private static final String PACKAGE = "com.foo";
+
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInt;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInt;
+ @Mock
+ private BatteryStatsService mBatteryStatsService;
+
+ private ActivityManagerService mRealAms;
+ private ActivityManagerService mAms;
+
+ private ProcessList mRealProcessList = new ProcessList();
+ private ProcessList mProcessList;
+
+ final IProcessObserver mProcessObserver = mock(IProcessObserver.Stub.class);
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+ doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+ mRealAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ mRealAms.mConstants.loadDeviceConfigConstants();
+ mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mRealAms.mAtmInternal = mActivityTaskManagerInt;
+ mRealAms.mPackageManagerInt = mPackageManagerInt;
+ mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+ mRealAms.mProcessesReady = true;
+ mAms = spy(mRealAms);
+ mRealProcessList.mService = mAms;
+ mProcessList = spy(mRealProcessList);
+
+ doReturn(mProcessObserver).when(mProcessObserver).asBinder();
+ mProcessList.registerProcessObserver(mProcessObserver);
+
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+ + Arrays.toString(invocation.getArguments()));
+ return null;
+ }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mRealProcessList;
+ }
+
+ @Override
+ public BatteryStatsService getBatteryStatsService() {
+ return mBatteryStatsService;
+ }
+ }
+
+ private ProcessRecord makeActiveProcessRecord(String packageName)
+ throws Exception {
+ final ApplicationInfo ai = makeApplicationInfo(packageName);
+ return makeActiveProcessRecord(ai);
+ }
+
+ private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai)
+ throws Exception {
+ final IApplicationThread thread = mock(IApplicationThread.class);
+ final IBinder threadBinder = new Binder();
+ doReturn(threadBinder).when(thread).asBinder();
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting bindApplication() for "
+ + Arrays.toString(invocation.getArguments()));
+ if (mRealAms.mConstants.mEnableWaitForFinishAttachApplication) {
+ mRealAms.finishAttachApplication(0);
+ }
+ return null;
+ }).when(thread).bindApplication(
+ any(), any(),
+ any(), any(), anyBoolean(),
+ any(), any(),
+ any(), any(),
+ any(),
+ any(), anyInt(),
+ anyBoolean(), anyBoolean(),
+ anyBoolean(), anyBoolean(), any(),
+ any(), any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ anyLong(), anyLong());
+ final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+ r.setPid(myPid());
+ r.setStartUid(myUid());
+ r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+ r.makeActive(thread, mAms.mProcessStats);
+ doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+ anyBoolean());
+ return r;
+ }
+
+ static ApplicationInfo makeApplicationInfo(String packageName) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.processName = packageName;
+ ai.uid = myUid();
+ return ai;
+ }
+
+ /**
+ * Verify that a process start event is dispatched to process observers.
+ */
+ @Test
+ public void testNormal() throws Exception {
+ ProcessRecord app = startProcess();
+ verify(mProcessObserver).onProcessStarted(
+ app.getPid(), app.uid, app.info.uid, PACKAGE, PACKAGE);
+ }
+
+ private ProcessRecord startProcess() throws Exception {
+ final ProcessRecord app = makeActiveProcessRecord(PACKAGE);
+ final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+ mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+ /* expectedStartSeq */ 0, /* procAttached */ false);
+ app.getThread().bindApplication(PACKAGE, appInfo,
+ null, null, false,
+ null,
+ null,
+ null, null,
+ null,
+ null, 0,
+ false, false,
+ true, false,
+ null,
+ null, null,
+ null,
+ null, null, null,
+ null, null,
+ 0, 0);
+ return app;
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 93a2eefba5b1..28471b37d2a0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -180,6 +180,7 @@ public class FlexibilityControllerTest {
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
// Initialize real objects.
+ doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
mFlexibilityController = new FlexibilityController(mJobSchedulerService,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 293391f43828..c6608e61fc62 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -45,6 +45,7 @@ import static com.android.server.job.controllers.JobStatus.CONSTRAINT_WITHIN_QUO
import static com.android.server.job.controllers.JobStatus.NO_EARLIEST_RUNTIME;
import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -78,6 +79,7 @@ import org.mockito.quality.Strictness;
import java.time.Clock;
import java.time.ZoneOffset;
+import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
public class JobStatusTest {
@@ -138,6 +140,35 @@ public class JobStatusTest {
}
@Test
+ public void testApplyBasicPiiFilters_email() {
+ assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test@email.com"));
+ assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("test+plus@email.com"));
+ assertEquals("[EMAIL]", JobStatus.applyBasicPiiFilters("t.e_st+plus-minus@email.com"));
+
+ assertEquals("prefix:[EMAIL]", JobStatus.applyBasicPiiFilters("prefix:test@email.com"));
+
+ assertEquals("not-an-email", JobStatus.applyBasicPiiFilters("not-an-email"));
+ }
+
+ @Test
+ public void testApplyBasicPiiFilters_mixture() {
+ assertEquals("[PHONE]:[EMAIL]",
+ JobStatus.applyBasicPiiFilters("123-456-7890:test+plus@email.com"));
+ assertEquals("prefix:[PHONE]:[EMAIL]",
+ JobStatus.applyBasicPiiFilters("prefix:123-456-7890:test+plus@email.com"));
+ }
+
+ @Test
+ public void testApplyBasicPiiFilters_phone() {
+ assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("123-456-7890"));
+ assertEquals("[PHONE]", JobStatus.applyBasicPiiFilters("+1-234-567-8900"));
+
+ assertEquals("prefix:[PHONE]", JobStatus.applyBasicPiiFilters("prefix:123-456-7890"));
+
+ assertEquals("not-a-phone-number", JobStatus.applyBasicPiiFilters("not-a-phone-number"));
+ }
+
+ @Test
public void testCanRunInBatterySaver_regular() {
final JobInfo jobInfo =
new JobInfo.Builder(101, new ComponentName("foo", "bar")).build();
@@ -245,6 +276,42 @@ public class JobStatusTest {
}
@Test
+ public void testGetFilteredDebugTags() {
+ final JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .addDebugTag("test@email.com")
+ .addDebugTag("123-456-7890")
+ .addDebugTag("random")
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ String[] expected = new String[]{"[EMAIL]", "[PHONE]", "random"};
+ String[] result = job.getFilteredDebugTags();
+ Arrays.sort(expected);
+ Arrays.sort(result);
+ assertArrayEquals(expected, result);
+ }
+
+ @Test
+ public void testGetFilteredTraceTag() {
+ JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("test@email.com")
+ .build();
+ JobStatus job = createJobStatus(jobInfo);
+ assertEquals("[EMAIL]", job.getFilteredTraceTag());
+
+ jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("123-456-7890")
+ .build();
+ job = createJobStatus(jobInfo);
+ assertEquals("[PHONE]", job.getFilteredTraceTag());
+
+ jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+ .setTraceTag("random")
+ .build();
+ job = createJobStatus(jobInfo);
+ assertEquals("random", job.getFilteredTraceTag());
+ }
+
+ @Test
public void testIsUserVisibleJob() {
JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
.setUserInitiated(false)
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
index 8d9a6c510576..9a143d5b3743 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/altitude/AltitudeConverterTest.java
@@ -21,6 +21,8 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.content.Context;
+import android.frameworks.location.altitude.GetGeoidHeightRequest;
+import android.frameworks.location.altitude.GetGeoidHeightResponse;
import android.location.Location;
import android.location.altitude.AltitudeConverter;
@@ -176,4 +178,20 @@ public class AltitudeConverterTest {
assertThrows(IllegalArgumentException.class,
() -> mAltitudeConverter.addMslAltitudeToLocation(mContext, location));
}
+
+ @Test
+ public void testGetGeoidHeight_expectedBehavior() throws IOException {
+ GetGeoidHeightRequest request = new GetGeoidHeightRequest();
+ request.latitudeDegrees = -35.334815;
+ request.longitudeDegrees = -45;
+ // Requires data to be loaded from raw assets.
+ GetGeoidHeightResponse response = mAltitudeConverter.getGeoidHeight(mContext, request);
+ assertThat(response.geoidHeightMeters).isWithin(2).of(-5.0622);
+ assertThat(response.geoidHeightErrorMeters).isGreaterThan(0f);
+ assertThat(response.geoidHeightErrorMeters).isLessThan(1f);
+ assertThat(response.expirationDistanceMeters).isWithin(1).of(-6.33);
+ assertThat(response.additionalGeoidHeightErrorMeters).isGreaterThan(0f);
+ assertThat(response.additionalGeoidHeightErrorMeters).isLessThan(1f);
+ assertThat(response.success).isTrue();
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 4eba21934a4e..efab19cc9663 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -29,6 +29,7 @@ import static org.mockito.MockitoAnnotations.initMocks;
import android.content.Context;
import android.location.Location;
+import android.location.LocationRequest;
import android.location.LocationResult;
import android.location.provider.ProviderRequest;
import android.platform.test.annotations.Presubmit;
@@ -218,4 +219,22 @@ public class StationaryThrottlingLocationProviderTest {
verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
}
+
+ @Test
+ public void testNoThrottle_highAccuracy() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(
+ 50).setQuality(LocationRequest.QUALITY_HIGH_ACCURACY).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ LocationResult loc = createLocationResult("test_provider", mRandom);
+ mDelegateProvider.reportLocation(loc);
+ verify(mListener, times(1)).onReportLocation(loc);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, after(75).times(1)).onReportLocation(any(LocationResult.class));
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 5bec903e6414..656bc71eebca 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -556,7 +556,7 @@ public final class UserManagerServiceTest {
@Test
public void testCreateUserWithLongName_TruncatesName() {
UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
- assertThat(user.name.length()).isEqualTo(500);
+ assertThat(user.name.length()).isEqualTo(UserManager.MAX_USER_NAME_LENGTH);
UserInfo user1 = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0);
assertThat(user1.name.length()).isEqualTo(4);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.java
new file mode 100644
index 000000000000..01c7fbe5bfe9
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/RateLimiterTest.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.server.selinux;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class RateLimiterTest {
+
+ private final MockClock mMockClock = new MockClock();
+
+ @Test
+ public void testRateLimiter_1QPS() {
+ RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofSeconds(1));
+
+ // First acquire is granted.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ // Next acquire is negated because it's too soon.
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ // Wait >=1 seconds.
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ }
+
+ @Test
+ public void testRateLimiter_3QPS() {
+ RateLimiter rateLimiter =
+ new RateLimiter(
+ mMockClock,
+ Duration.ofSeconds(1).dividedBy(3).truncatedTo(ChronoUnit.MILLIS));
+
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(2).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(3).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1).dividedBy(4).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ }
+
+ @Test
+ public void testRateLimiter_infiniteQPS() {
+ RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(0));
+
+ // so many permits.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+
+ mMockClock.currentTimeMillis += Duration.ofSeconds(10).toMillis();
+ // still so many permits.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+
+ mMockClock.currentTimeMillis += Duration.ofDays(-10).toMillis();
+ // only going backwards in time you will stop the permits.
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ assertThat(rateLimiter.tryAcquire()).isFalse();
+ }
+
+ @Test
+ public void testRateLimiter_negativeQPS() {
+ RateLimiter rateLimiter = new RateLimiter(mMockClock, Duration.ofMillis(-10));
+
+ // Negative QPS is effectively turning of the rate limiter.
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ mMockClock.currentTimeMillis += Duration.ofSeconds(1000).toMillis();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ assertThat(rateLimiter.tryAcquire()).isTrue();
+ }
+
+ private static final class MockClock extends Clock {
+
+ public long currentTimeMillis = 0;
+
+ @Override
+ public long currentTimeMillis() {
+ return currentTimeMillis;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
new file mode 100644
index 000000000000..b36c9bdaf456
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsBuilderTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.selinux;
+
+import static com.android.server.selinux.SelinuxAuditLogBuilder.PATH_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.SCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.TCONTEXT_MATCHER;
+import static com.android.server.selinux.SelinuxAuditLogBuilder.toCategories;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.server.selinux.SelinuxAuditLogBuilder.SelinuxAuditLog;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsBuilderTest {
+
+ private final SelinuxAuditLogBuilder mAuditLogBuilder = new SelinuxAuditLogBuilder();
+
+ @Test
+ public void testMatcher_scontext() {
+ assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0").matches()).isTrue();
+ assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+ assertThat(SCONTEXT_MATCHER.group("scategories")).isNull();
+
+ assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:c123,c456").matches()).isTrue();
+ assertThat(SCONTEXT_MATCHER.group("stype")).isEqualTo("sdk_sandbox_audit");
+ assertThat(toCategories(SCONTEXT_MATCHER.group("scategories")))
+ .isEqualTo(new int[] {123, 456});
+
+ assertThat(SCONTEXT_MATCHER.reset("u:r:not_sdk_sandbox:s0").matches()).isFalse();
+ assertThat(SCONTEXT_MATCHER.reset("u:object_r:sdk_sandbox_audit:s0").matches()).isFalse();
+ assertThat(SCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:p123").matches()).isFalse();
+ }
+
+ @Test
+ public void testMatcher_tcontext() {
+ assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type:s0").matches()).isTrue();
+ assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type");
+ assertThat(TCONTEXT_MATCHER.group("tcategories")).isNull();
+
+ assertThat(TCONTEXT_MATCHER.reset("u:object_r:target_type2:s0:c666").matches()).isTrue();
+ assertThat(TCONTEXT_MATCHER.group("ttype")).isEqualTo("target_type2");
+ assertThat(toCategories(TCONTEXT_MATCHER.group("tcategories"))).isEqualTo(new int[] {666});
+
+ assertThat(TCONTEXT_MATCHER.reset("u:r:target_type:s0").matches()).isFalse();
+ assertThat(TCONTEXT_MATCHER.reset("u:r:sdk_sandbox_audit:s0:x456").matches()).isFalse();
+ }
+
+ @Test
+ public void testMatcher_path() {
+ assertThat(PATH_MATCHER.reset("\"/data\"").matches()).isTrue();
+ assertThat(PATH_MATCHER.group("path")).isEqualTo("/data");
+ assertThat(PATH_MATCHER.reset("\"/data/local\"").matches()).isTrue();
+ assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+ assertThat(PATH_MATCHER.reset("\"/data/local/tmp\"").matches()).isTrue();
+ assertThat(PATH_MATCHER.group("path")).isEqualTo("/data/local");
+
+ assertThat(PATH_MATCHER.reset("\"/data/local").matches()).isFalse();
+ assertThat(PATH_MATCHER.reset("\"_data_local\"").matches()).isFalse();
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_noOptionals() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0"
+ + " tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(), true, new String[] {"p"}, "sdk_sandbox_audit", "t", "c");
+
+ mAuditLogBuilder.reset(
+ "tclass=c2 granted { p2 } tcontext=u:object_r:t2:s0"
+ + " scontext=u:r:sdk_sandbox_audit:s0");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p2"},
+ "sdk_sandbox_audit",
+ "t2",
+ "c2");
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_withCategories() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0:c123"
+ + " tcontext=u:object_r:t:s0:c456,c666 tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "t",
+ new int[] {456, 666},
+ "c",
+ null,
+ false);
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_withPath() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 path=\"/very/long/path\""
+ + " tcontext=u:object_r:t:s0 tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ null,
+ "t",
+ null,
+ "c",
+ "/very/long",
+ false);
+ }
+
+ @Test
+ public void testSelinuxAuditLogsBuilder_withPermissive() {
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 permissive=0"
+ + " tcontext=u:object_r:t:s0 tclass=c");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ null,
+ "t",
+ null,
+ "c",
+ null,
+ false);
+
+ mAuditLogBuilder.reset(
+ "granted { p } scontext=u:r:sdk_sandbox_audit:s0 tcontext=u:object_r:t:s0 tclass=c"
+ + " permissive=1");
+ assertAuditLog(
+ mAuditLogBuilder.build(),
+ true,
+ new String[] {"p"},
+ "sdk_sandbox_audit",
+ null,
+ "t",
+ null,
+ "c",
+ null,
+ true);
+ }
+
+ private void assertAuditLog(
+ SelinuxAuditLog auditLog,
+ boolean granted,
+ String[] permissions,
+ String sType,
+ String tType,
+ String tClass) {
+ assertAuditLog(
+ auditLog, granted, permissions, sType, null, tType, null, tClass, null, false);
+ }
+
+ private void assertAuditLog(
+ SelinuxAuditLog auditLog,
+ boolean granted,
+ String[] permissions,
+ String sType,
+ int[] sCategories,
+ String tType,
+ int[] tCategories,
+ String tClass,
+ String path,
+ boolean permissive) {
+ assertThat(auditLog).isNotNull();
+ assertThat(auditLog.mGranted).isEqualTo(granted);
+ assertThat(auditLog.mPermissions).isEqualTo(permissions);
+ assertThat(auditLog.mSType).isEqualTo(sType);
+ assertThat(auditLog.mSCategories).isEqualTo(sCategories);
+ assertThat(auditLog.mTType).isEqualTo(tType);
+ assertThat(auditLog.mTCategories).isEqualTo(tCategories);
+ assertThat(auditLog.mTClass).isEqualTo(tClass);
+ assertThat(auditLog.mPath).isEqualTo(path);
+ assertThat(auditLog.mPermissive).isEqualTo(permissive);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
new file mode 100644
index 000000000000..9758ea596335
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/selinux/SelinuxAuditLogsCollectorTest.java
@@ -0,0 +1,644 @@
+/*
+ * 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.selinux;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+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;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.util.EventLog;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.os.Clock;
+import com.android.internal.util.FrameworkStatsLog;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+@RunWith(AndroidJUnit4.class)
+public class SelinuxAuditLogsCollectorTest {
+
+ // Fake tag to use for testing
+ private static final int ANSWER_TAG = 42;
+
+ private final MockClock mClock = new MockClock();
+
+ private final SelinuxAuditLogsCollector mSelinuxAutidLogsCollector =
+ // Ignore rate limiting for tests
+ new SelinuxAuditLogsCollector(
+ new RateLimiter(mClock, /* window= */ Duration.ofMillis(0)),
+ new QuotaLimiter(
+ mClock, /* windowSize= */ Duration.ofHours(1), /* maxPermits= */ 5));
+
+ private MockitoSession mMockitoSession;
+
+ @Before
+ public void setUp() {
+ // move the clock forward for the limiters.
+ mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+ // Ignore what was written in the event logs by previous tests.
+ mSelinuxAutidLogsCollector.mLastWrite = Instant.now();
+
+ mMockitoSession =
+ mockitoSession().initMocks(this).mockStatic(FrameworkStatsLog.class).startMocking();
+ }
+
+ @After
+ public void tearDown() {
+ mMockitoSession.finishMocking();
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs() {
+ writeTestLog("granted", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm1", "sdk_sandbox_audit", "ttype1", "tclass1");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ true,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm1"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype1",
+ null,
+ "tclass1",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_multiplePerms() {
+ writeTestLog("denied", "perm1 perm2", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm3 perm4", "sdk_sandbox_audit", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm1", "perm2"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm3", "perm4"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_withPaths() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/good/path");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/very/long/path");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "/short_path");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", "not_a_path");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ "/good/path",
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ "/very/long",
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ "/short_path",
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_withCategories() {
+ writeTestLog(
+ "denied", "perm", "sdk_sandbox_audit", new int[] {123}, "ttype", null, "tclass");
+ writeTestLog(
+ "denied",
+ "perm",
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ null,
+ "tclass");
+ writeTestLog(
+ "denied", "perm", "sdk_sandbox_audit", null, "ttype", new int[] {666}, "tclass");
+ writeTestLog(
+ "denied",
+ "perm",
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ new int[] {666, 777},
+ "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ new int[] {666},
+ "tclass",
+ null,
+ false));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123, 456},
+ "ttype",
+ new int[] {666, 777},
+ "tclass",
+ null,
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_withPathAndCategories() {
+ writeTestLog(
+ "denied",
+ "perm",
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "ttype",
+ new int[] {666},
+ "tclass",
+ "/a/path");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ new int[] {123},
+ "ttype",
+ new int[] {666},
+ "tclass",
+ "/a/path",
+ false));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_permissive() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", true);
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass", false);
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ false),
+ times(2));
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.SELINUX_AUDIT_LOG,
+ false,
+ new String[] {"perm"},
+ "sdk_sandbox_audit",
+ null,
+ "ttype",
+ null,
+ "tclass",
+ null,
+ true));
+ }
+
+ @Test
+ public void testNotWriteAuditLogs_notSdkSandbox() {
+ writeTestLog("denied", "perm", "stype", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ never());
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_upToQuota() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ // These are not pushed.
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(5));
+ }
+
+ @Test
+ public void testWriteSdkSandboxAuditLogs_resetQuota() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(5));
+
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ // move the clock forward to reset the quota limiter.
+ mClock.currentTimeMillis += Duration.ofHours(1).toMillis();
+ done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(10));
+ }
+
+ @Test
+ public void testNotWriteAuditLogs_stopRequested() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ // These are not pushed.
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+
+ mSelinuxAutidLogsCollector.mStopRequested.set(true);
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isFalse();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ never());
+
+ mSelinuxAutidLogsCollector.mStopRequested.set(false);
+ done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+ assertThat(done).isTrue();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ times(5));
+ }
+
+ @Test
+ public void testAuditLogs_resumeJobDoesNotExceedLimit() {
+ writeTestLog("denied", "perm", "sdk_sandbox_audit", "ttype", "tclass");
+ mSelinuxAutidLogsCollector.mStopRequested.set(true);
+
+ boolean done = mSelinuxAutidLogsCollector.collect(ANSWER_TAG);
+
+ assertThat(done).isFalse();
+ verify(
+ () ->
+ FrameworkStatsLog.write(
+ anyInt(),
+ anyBoolean(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyString(),
+ any(),
+ anyBoolean()),
+ never());
+ }
+
+ private static void writeTestLog(
+ String granted, String permissions, String sType, String tType, String tClass) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s",
+ granted, permissions, sType, tType, tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ String tType,
+ String tClass,
+ String path) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0"
+ + " tclass=%s",
+ granted, permissions, path, sType, tType, tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ int[] sCategories,
+ String tType,
+ int[] tCategories,
+ String tClass) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } scontext=u:r:%s:s0%s tcontext=u:object_r:%s:s0%s tclass=%s",
+ granted,
+ permissions,
+ sType,
+ toCategoriesString(sCategories),
+ tType,
+ toCategoriesString(tCategories),
+ tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ int[] sCategories,
+ String tType,
+ int[] tCategories,
+ String tClass,
+ String path) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } path=\"%s\" scontext=u:r:%s:s0%s"
+ + " tcontext=u:object_r:%s:s0%s tclass=%s",
+ granted,
+ permissions,
+ path,
+ sType,
+ toCategoriesString(sCategories),
+ tType,
+ toCategoriesString(tCategories),
+ tClass));
+ }
+
+ private static void writeTestLog(
+ String granted,
+ String permissions,
+ String sType,
+ String tType,
+ String tClass,
+ boolean permissive) {
+ EventLog.writeEvent(
+ ANSWER_TAG,
+ String.format(
+ "avc: %s { %s } scontext=u:r:%s:s0 tcontext=u:object_r:%s:s0 tclass=%s"
+ + " permissive=%s",
+ granted, permissions, sType, tType, tClass, permissive ? "1" : "0"));
+ }
+
+ private static String toCategoriesString(int[] categories) {
+ return (categories == null || categories.length == 0)
+ ? ""
+ : ":c"
+ + Arrays.stream(categories)
+ .mapToObj(String::valueOf)
+ .collect(Collectors.joining(",c"));
+ }
+
+ private static final class MockClock extends Clock {
+
+ public long currentTimeMillis = 0;
+
+ @Override
+ public long currentTimeMillis() {
+ return currentTimeMillis;
+ }
+ }
+}
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 654d7a8de168..f49f6383b3c8 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -44,6 +44,7 @@ android_test {
"servicestests-utils",
"platform-test-annotations",
"flag-junit",
+ "ravenwood-junit",
],
libs: [
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index ca162e0b46e1..ba2b53854cd7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -32,6 +32,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.UidBatteryConsumer;
import android.os.UserBatteryConsumer;
+import android.platform.test.ravenwood.RavenwoodRule;
import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -57,7 +58,8 @@ public class BatteryUsageStatsRule implements TestRule {
private final PowerProfile mPowerProfile;
private final MockClock mMockClock = new MockClock();
- private final MockBatteryStatsImpl mBatteryStats;
+ private final File mHistoryDir;
+ private MockBatteryStatsImpl mBatteryStats;
private Handler mHandler;
private BatteryUsageStats mBatteryUsageStats;
@@ -66,6 +68,10 @@ public class BatteryUsageStatsRule implements TestRule {
private SparseArray<int[]> mCpusByPolicy = new SparseArray<>();
private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>();
+ private int mDisplayCount = -1;
+ private int mPerUidModemModel = -1;
+ private NetworkStats mNetworkStats;
+
public BatteryUsageStatsRule() {
this(0, null);
}
@@ -78,16 +84,38 @@ public class BatteryUsageStatsRule implements TestRule {
mHandler = mock(Handler.class);
mPowerProfile = spy(new PowerProfile());
mMockClock.currentTime = currentTime;
- mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir, mHandler);
- mBatteryStats.setPowerProfile(mPowerProfile);
+ mHistoryDir = historyDir;
+
+ if (!RavenwoodRule.isUnderRavenwood()) {
+ lateInitBatteryStats();
+ }
mCpusByPolicy.put(0, new int[]{0, 1, 2, 3});
mCpusByPolicy.put(4, new int[]{4, 5, 6, 7});
mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000});
mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000});
+ }
+
+ private void lateInitBatteryStats() {
+ if (mBatteryStats != null) return;
+
+ mBatteryStats = new MockBatteryStatsImpl(mMockClock, mHistoryDir, mHandler);
+ mBatteryStats.setPowerProfile(mPowerProfile);
mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
mBatteryStats.onSystemReady();
+
+ if (mDisplayCount != -1) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
+ if (mPerUidModemModel != -1) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
+ }
+ if (mNetworkStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
public MockClock getMockClock() {
@@ -112,7 +140,10 @@ public class BatteryUsageStatsRule implements TestRule {
}
mCpusByPolicy.put(policy, relatedCpus);
mFreqsByPolicy.put(policy, frequencies);
- mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ if (mBatteryStats != null) {
+ mBatteryStats.setCpuScalingPolicies(
+ new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy));
+ }
return this;
}
@@ -174,13 +205,19 @@ public class BatteryUsageStatsRule implements TestRule {
public BatteryUsageStatsRule setNumDisplays(int value) {
when(mPowerProfile.getNumDisplays()).thenReturn(value);
- mBatteryStats.setDisplayCountLocked(value);
+ mDisplayCount = value;
+ if (mBatteryStats != null) {
+ mBatteryStats.setDisplayCountLocked(mDisplayCount);
+ }
return this;
}
public BatteryUsageStatsRule setPerUidModemModel(int perUidModemModel) {
- synchronized (mBatteryStats) {
- mBatteryStats.setPerUidModemModel(perUidModemModel);
+ mPerUidModemModel = perUidModemModel;
+ if (mBatteryStats != null) {
+ synchronized (mBatteryStats) {
+ mBatteryStats.setPerUidModemModel(mPerUidModemModel);
+ }
}
return this;
}
@@ -210,7 +247,10 @@ public class BatteryUsageStatsRule implements TestRule {
}
public void setNetworkStats(NetworkStats networkStats) {
- mBatteryStats.setNetworkStats(networkStats);
+ mNetworkStats = networkStats;
+ if (mBatteryStats != null) {
+ mBatteryStats.setNetworkStats(mNetworkStats);
+ }
}
@Override
@@ -225,6 +265,7 @@ public class BatteryUsageStatsRule implements TestRule {
}
private void before() {
+ lateInitBatteryStats();
HandlerThread bgThread = new HandlerThread("bg thread");
bgThread.start();
mHandler = new Handler(bgThread.getLooper());
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index be68e9c3c01f..8958fac87bb6 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -31,6 +31,10 @@ android_test {
"test-apps/SuspendTestApp/src/**/*.java",
],
+
+ kotlincflags: [
+ "-Werror",
+ ],
static_libs: [
"frameworks-base-testutils",
"services.accessibility",
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 88b2ed4f79c9..071db68704af 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -491,6 +492,22 @@ public class AuthServiceTest {
}
@Test
+ public void testRegisterAuthenticationStateListener_callsFaceService() throws Exception {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ final AuthenticationStateListener listener = mock(AuthenticationStateListener.class);
+
+ mAuthService.mImpl.registerAuthenticationStateListener(listener);
+
+ waitForIdle();
+ verify(mFaceService).registerAuthenticationStateListener(eq(listener));
+ }
+
+ @Test
public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
throws Exception {
setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
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 3a3dd6ea2746..f8b5b04294cd 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,7 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
@@ -49,6 +50,7 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.TestableContext;
import androidx.test.filters.SmallTest;
@@ -58,6 +60,7 @@ import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.OperationContextExt;
import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutTracker;
@@ -81,6 +84,8 @@ import java.util.function.Consumer;
@SmallTest
public class FaceAuthenticationClientTest {
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int USER_ID = 12;
private static final long OP_ID = 32;
private static final int WAKE_REASON = WakeReason.LIFT;
@@ -105,6 +110,8 @@ public class FaceAuthenticationClientTest {
@Mock
private ClientMonitorCallback mCallback;
@Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
+ @Mock
private AidlResponseHandler mAidlResponseHandler;
@Mock
private ActivityTaskManager mActivityTaskManager;
@@ -264,6 +271,29 @@ public class FaceAuthenticationClientTest {
verify(mHal, never()).authenticate(anyInt());
}
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FaceAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Face("friendly", 1 /* faceId */, 2 /* deviceId */),
+ false /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+ }
+
private FaceAuthenticationClient createClient() throws RemoteException {
return createClient(2 /* version */, mClientMonitorCallbackConverter,
false /* allowBackgroundAuthentication */,
@@ -311,7 +341,8 @@ public class FaceAuthenticationClientTest {
false /* requireConfirmation */,
mBiometricLogger, mBiometricContext, true /* isStrongBiometric */,
mUsageStats, lockoutTracker, allowBackgroundAuthentication,
- null /* sensorPrivacyManager */, 0 /* biometricStrength */) {
+ null /* sensorPrivacyManager */, 0 /* biometricStrength */,
+ mAuthenticationStateListeners) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 772ec8b73393..7648bd17f53c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -51,6 +51,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -89,6 +90,8 @@ public class FaceProviderTest {
private BiometricContext mBiometricContext;
@Mock
private BiometricStateCallback mBiometricStateCallback;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
private final TestLooper mLooper = new TestLooper();
private SensorProps[] mSensorProps;
@@ -119,8 +122,8 @@ public class FaceProviderTest {
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
mFaceProvider = new FaceProvider(mContext, mBiometricStateCallback,
- mSensorProps, TAG, mLockoutResetDispatcher, mBiometricContext,
- mDaemon, new Handler(mLooper.getLooper()),
+ mAuthenticationStateListeners, mSensorProps, TAG, mLockoutResetDispatcher,
+ mBiometricContext, mDaemon, new Handler(mLooper.getLooper()),
false /* resetLockoutRequiresChallenge */, false /* testHalEnabled */);
}
@@ -154,7 +157,7 @@ public class FaceProviderTest {
final HidlFaceSensorConfig[] hidlFaceSensorConfig =
new HidlFaceSensorConfig[]{faceSensorConfig};
mFaceProvider = new FaceProvider(mContext,
- mBiometricStateCallback, hidlFaceSensorConfig, TAG,
+ mBiometricStateCallback, mAuthenticationStateListeners, hidlFaceSensorConfig, TAG,
mLockoutResetDispatcher, mBiometricContext, mDaemon,
new Handler(mLooper.getLooper()),
true /* resetLockoutRequiresChallenge */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index e558c4d64180..78c1e08ba832 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -44,6 +44,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -81,6 +82,8 @@ public class Face10Test {
private BiometricContext mBiometricContext;
@Mock
private BiometricStateCallback mBiometricStateCallback;
+ @Mock
+ private AuthenticationStateListeners mAuthenticationStateListeners;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -116,8 +119,8 @@ public class Face10Test {
Face10.sSystemClock = Clock.fixed(
Instant.ofEpochMilli(100), ZoneId.of("America/Los_Angeles"));
- mFace10 = new Face10(mContext, mBiometricStateCallback, sensorProps,
- mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
+ mFace10 = new Face10(mContext, mBiometricStateCallback, mAuthenticationStateListeners,
+ sensorProps, mLockoutResetDispatcher, mHandler, mScheduler, mBiometricContext);
mBinder = new Binder();
}
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 774ea5bc6b16..4ed6f74d30fa 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
@@ -16,6 +16,7 @@
package com.android.server.biometrics.sensors.fingerprint.aidl;
+import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
@@ -451,6 +452,29 @@ public class FingerprintAuthenticationClientTest {
}
@Test
+ public void testAuthenticationStateListeners_onAuthenticationSucceeded()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), true /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationSucceeded(anyInt(), anyInt());
+ }
+
+ @Test
+ public void testAuthenticationStateListeners_onAuthenticationFailed() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS);
+ final FingerprintAuthenticationClient client = createClient();
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */,
+ 2 /* deviceId */), false /* authenticated */, new ArrayList<>());
+
+ verify(mAuthenticationStateListeners).onAuthenticationFailed(anyInt(), anyInt());
+ }
+
+ @Test
public void cancelsAuthWhenNotInForeground() throws Exception {
final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
topTask.topActivity = new ComponentName("other", "thing");
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index ccbbaa52ac21..5943832586b3 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -33,19 +33,21 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import android.view.Display;
import android.view.DisplayInfo;
import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
+import com.android.input.flags.Flags;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -58,6 +60,9 @@ public class InputControllerTest {
private static final String LANGUAGE_TAG = "en-US";
private static final String LAYOUT_TYPE = "qwerty";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
@@ -72,11 +77,12 @@ public class InputControllerTest {
@Before
public void setUp() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
+
MockitoAnnotations.initMocks(this);
mInputManagerMockHelper = new InputManagerMockHelper(
TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
- doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
@@ -129,11 +135,7 @@ public class InputControllerTest {
mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
- doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
mInputController.unregisterInputDevice(deviceToken);
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(
- eq(Display.INVALID_DISPLAY));
}
@Test
@@ -143,14 +145,11 @@ public class InputControllerTest {
mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
/* displayId= */ 1);
verify(mNativeWrapperMock).openUinputMouse(eq("mouse1"), eq(1), eq(1), anyString());
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
final IBinder deviceToken2 = new Binder();
mInputController.createMouse("mouse2", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
/* displayId= */ 2);
verify(mNativeWrapperMock).openUinputMouse(eq("mouse2"), eq(1), eq(1), anyString());
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2));
mInputController.unregisterInputDevice(deviceToken);
- verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1));
}
@Test
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 9ff29d208dc0..5442af875e86 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
@@ -339,8 +339,8 @@ public class VirtualDeviceManagerServiceTest {
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
+ mSetFlagsRule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER);
- doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
doNothing().when(mInputManagerInternalMock)
.setMousePointerAccelerationEnabled(anyBoolean(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
@@ -1333,7 +1333,6 @@ public class VirtualDeviceManagerServiceTest {
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS,
DEVICE_NAME_1, INPUT_DEVICE_ID);
- doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
assertThat(mDeviceImpl.sendButtonEvent(BINDER,
new VirtualMouseButtonEvent.Builder()
.setButtonCode(buttonCode)
@@ -1363,7 +1362,6 @@ public class VirtualDeviceManagerServiceTest {
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
INPUT_DEVICE_ID);
- doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
assertThat(mDeviceImpl.sendRelativeEvent(BINDER,
new VirtualMouseRelativeEvent.Builder()
.setRelativeX(x)
@@ -1394,7 +1392,6 @@ public class VirtualDeviceManagerServiceTest {
mInputController.addDeviceForTesting(BINDER, fd,
InputController.InputDeviceDescriptor.TYPE_MOUSE, DISPLAY_ID_1, PHYS, DEVICE_NAME_1,
INPUT_DEVICE_ID);
- doReturn(DISPLAY_ID_1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
assertThat(mDeviceImpl.sendScrollEvent(BINDER,
new VirtualMouseScrollEvent.Builder()
.setXAxisMovement(x)
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
index 3e4f1df0e1d4..81981e6b16ca 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/camera/VirtualCameraControllerTest.java
@@ -183,9 +183,8 @@ public class VirtualCameraControllerTest {
private VirtualCameraConfig createVirtualCameraConfig(
int width, int height, int format, int maximumFramesPerSecond,
String name, int sensorOrientation, int lensFacing) {
- return new VirtualCameraConfig.Builder()
+ return new VirtualCameraConfig.Builder(name)
.addStreamConfig(width, height, format, maximumFramesPerSecond)
- .setName(name)
.setVirtualCameraCallback(mCallbackHandler, mVirtualCameraCallbackMock)
.setSensorOrientation(sensorOrientation)
.setLensFacing(lensFacing)
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 10f27ca02aaa..72fa949301cc 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -80,7 +80,7 @@ class OverlayActorEnforcerTests {
@BeforeClass
@JvmStatic
fun checkAllCasesUniquelyNamed() {
- val duplicateCaseNames = CASES.mapIndexed { caseIndex, testCase ->
+ val duplicateCaseNames = CASES.mapIndexed { _, testCase ->
testCase.failures.map {
makeTestName(testCase, it.first, Params.Type.FAILURE)
} + testCase.allowed.map {
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index dc1d2c5e54b6..1c6d36b0a0d2 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -17,16 +17,19 @@
package com.android.server.os;
import android.app.admin.flags.Flags;
-import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
import android.app.role.RoleManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
import android.os.IBinder;
@@ -48,6 +51,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.util.concurrent.CompletableFuture;
@@ -66,6 +71,9 @@ public class BugreportManagerServiceImplTest {
private BugreportManagerServiceImpl mService;
private BugreportManagerServiceImpl.BugreportFileManager mBugreportFileManager;
+ @Mock
+ private PackageManager mPackageManager;
+
private int mCallingUid = 1234;
private String mCallingPackage = "test.package";
private AtomicFile mMappingFile;
@@ -74,7 +82,8 @@ public class BugreportManagerServiceImplTest {
private String mBugreportFile2 = "bugreport-file2.zip";
@Before
- public void setUp() {
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getInstrumentation().getContext();
mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
@@ -83,6 +92,7 @@ public class BugreportManagerServiceImplTest {
new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
mMappingFile));
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
+ when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
}
@After
@@ -115,12 +125,13 @@ public class BugreportManagerServiceImplTest {
assertThrows(IllegalArgumentException.class, () ->
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, Process.myUserHandle().getIdentifier(),
- "unknown-file.zip", /* forceUpdateMapping= */ true));
+ mContext, mPackageManager, callingInfo,
+ Process.myUserHandle().getIdentifier(), "unknown-file.zip",
+ /* forceUpdateMapping= */ true));
// No exception should be thrown.
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
/* forceUpdateMapping= */ true);
}
@@ -132,7 +143,7 @@ public class BugreportManagerServiceImplTest {
callingInfo, mBugreportFile, /* keepOnRetrieval= */ true);
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
/* forceUpdateMapping= */ true);
assertThat(mBugreportFileManager.mBugreportFilesToPersist).containsExactly(mBugreportFile);
@@ -148,10 +159,10 @@ public class BugreportManagerServiceImplTest {
// No exception should be thrown.
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile,
/* forceUpdateMapping= */ true);
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, mContext.getUserId(), mBugreportFile2,
+ mContext, mPackageManager, callingInfo, mContext.getUserId(), mBugreportFile2,
/* forceUpdateMapping= */ true);
}
@@ -160,8 +171,9 @@ public class BugreportManagerServiceImplTest {
Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage);
assertThrows(IllegalArgumentException.class,
() -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
- mContext, callingInfo, Process.myUserHandle().getIdentifier(),
- "test-file.zip", /* forceUpdateMapping= */ true));
+ mContext, mPackageManager, callingInfo,
+ Process.myUserHandle().getIdentifier(), "test-file.zip",
+ /* forceUpdateMapping= */ true));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index a743fff5d2ea..06be456be0db 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -19,6 +19,7 @@ package com.android.server.pm;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertEquals;
@@ -33,6 +34,7 @@ import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.os.PersistableBundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Postsubmit;
@@ -1632,6 +1634,106 @@ public final class UserManagerTest {
assertThat(mainUserCount).isEqualTo(1);
}
+ @Test
+ public void testAddUserAccountData_validStringValuesAreSaved_validBundleIsSaved() {
+ assumeManagedUsersSupported();
+
+ String userName = "User";
+ String accountName = "accountName";
+ String accountType = "accountType";
+ String arrayKey = "StringArrayKey";
+ String stringKey = "StringKey";
+ String intKey = "IntKey";
+ String nestedBundleKey = "PersistableBundleKey";
+ String value1 = "Value 1";
+ String value2 = "Value 2";
+ String value3 = "Value 3";
+
+ UserInfo userInfo = mUserManager.createUser(userName,
+ UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+ PersistableBundle accountOptions = new PersistableBundle();
+ String[] stringArray = {value1, value2};
+ accountOptions.putInt(intKey, 1234);
+ PersistableBundle nested = new PersistableBundle();
+ nested.putString(stringKey, value3);
+ accountOptions.putPersistableBundle(nestedBundleKey, nested);
+ accountOptions.putStringArray(arrayKey, stringArray);
+
+ mUserManager.clearSeedAccountData();
+ mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+ accountType, accountOptions);
+
+ //assert userName accountName and accountType were saved correctly
+ assertTrue(mUserManager.getUserInfo(userInfo.id).name.equals(userName));
+ assertTrue(mUserManager.getSeedAccountName().equals(accountName));
+ assertTrue(mUserManager.getSeedAccountType().equals(accountType));
+
+ //assert bundle with correct values was added
+ assertThat(mUserManager.getSeedAccountOptions().containsKey(arrayKey)).isTrue();
+ assertThat(mUserManager.getSeedAccountOptions().getPersistableBundle(nestedBundleKey)
+ .getString(stringKey)).isEqualTo(value3);
+ assertThat(mUserManager.getSeedAccountOptions().getStringArray(arrayKey)[0])
+ .isEqualTo(value1);
+
+ mUserManager.removeUser(userInfo.id);
+ }
+
+ @Test
+ public void testAddUserAccountData_invalidStringValuesAreTruncated_invalidBundleIsDropped() {
+ assumeManagedUsersSupported();
+
+ String tooLongString = generateLongString();
+ String userName = "User " + tooLongString;
+ String accountType = "Account Type " + tooLongString;
+ String accountName = "accountName " + tooLongString;
+ String arrayKey = "StringArrayKey";
+ String stringKey = "StringKey";
+ String intKey = "IntKey";
+ String nestedBundleKey = "PersistableBundleKey";
+ String value1 = "Value 1";
+ String value2 = "Value 2";
+
+ UserInfo userInfo = mUserManager.createUser(userName,
+ UserManager.USER_TYPE_FULL_SECONDARY, 0);
+
+ PersistableBundle accountOptions = new PersistableBundle();
+ String[] stringArray = {value1, value2};
+ accountOptions.putInt(intKey, 1234);
+ PersistableBundle nested = new PersistableBundle();
+ nested.putString(stringKey, tooLongString);
+ accountOptions.putPersistableBundle(nestedBundleKey, nested);
+ accountOptions.putStringArray(arrayKey, stringArray);
+ mUserManager.clearSeedAccountData();
+ mUserManager.setSeedAccountData(mContext.getUserId(), accountName,
+ accountType, accountOptions);
+
+ //assert userName was truncated
+ assertTrue(mUserManager.getUserInfo(userInfo.id).name.length()
+ == UserManager.MAX_USER_NAME_LENGTH);
+
+ //assert accountName and accountType got truncated
+ assertTrue(mUserManager.getSeedAccountName().length()
+ == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+ assertTrue(mUserManager.getSeedAccountType().length()
+ == UserManager.MAX_ACCOUNT_STRING_LENGTH);
+
+ //assert bundle with invalid values was dropped
+ assertThat(mUserManager.getSeedAccountOptions() == null).isTrue();
+
+ mUserManager.removeUser(userInfo.id);
+ }
+
+ private String generateLongString() {
+ String partialString = "Test Name Test Name Test Name Test Name Test Name Test Name Test "
+ + "Name Test Name Test Name Test Name "; //String of length 100
+ StringBuilder resultString = new StringBuilder();
+ for (int i = 0; i < 600; i++) {
+ resultString.append(partialString);
+ }
+ return resultString.toString();
+ }
+
private boolean isPackageInstalledForUser(String packageName, int userId) {
try {
return mPackageManager.getPackageInfoAsUser(packageName, 0, userId) != null;
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
index 150822bdff6b..c07c4d7618b7 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigNamedActorTest.kt
@@ -18,12 +18,13 @@ package com.android.server.systemconfig
import android.content.Context
import android.util.Xml
-import androidx.test.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.SystemConfig
import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
import org.junit.Rule
import org.junit.Test
-import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
class SystemConfigNamedActorTest {
@@ -37,14 +38,11 @@ class SystemConfigNamedActorTest {
private const val PACKAGE_TWO = "com.test.actor.two"
}
- private val context: Context = InstrumentationRegistry.getContext()
+ private val context: Context = InstrumentationRegistry.getInstrumentation().context
@get:Rule
val tempFolder = TemporaryFolder(context.filesDir)
- @get:Rule
- val expected = ExpectedException.none()
-
private var uniqueCounter = 0
@Test
@@ -193,11 +191,9 @@ class SystemConfigNamedActorTest {
</config>
""".write()
- expected.expect(IllegalStateException::class.java)
- expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " +
+ val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+ assertEquals(exc.message, "Defining $ACTOR_ONE as $PACKAGE_ONE " +
"for the android namespace is not allowed")
-
- assertPermissions()
}
@Test
@@ -217,11 +213,9 @@ class SystemConfigNamedActorTest {
</config>
""".write()
- expected.expect(IllegalStateException::class.java)
- expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
+ val exc = assertThrows(IllegalStateException::class.java) { assertPermissions() }
+ assertEquals(exc.message, "Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
" defined as both $PACKAGE_ONE and $PACKAGE_TWO")
-
- assertPermissions()
}
private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
@@ -230,5 +224,5 @@ class SystemConfigNamedActorTest {
private fun assertPermissions() = SystemConfig(false).apply {
val parser = Xml.newPullParser()
readPermissions(parser, tempFolder.root, 0)
- }. let { assertThat(it.namedActors) }
+ }.let { assertThat(it.namedActors) }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index bfd2df2d2b7d..e75afccfdfdf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -27,12 +27,13 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
+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;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.any;
@@ -43,6 +44,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -59,7 +61,10 @@ import android.app.Notification.Builder;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.Color;
@@ -80,6 +85,7 @@ import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -100,6 +106,7 @@ import com.android.server.pm.PackageManagerService;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -132,6 +139,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
KeyguardManager mKeyguardManager;
@Mock
private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
1 << 30);
@@ -171,11 +180,14 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
private static final int CUSTOM_LIGHT_OFF = 10000;
private static final int MAX_VIBRATION_DELAY = 1000;
private static final float DEFAULT_VOLUME = 1.0f;
+ private BroadcastReceiver mAvalancheBroadcastReceiver;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
getContext().addMockSystemService(Vibrator.class, mVibrator);
+ getContext().addMockSystemService(PackageManager.class, mPackageManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false);
when(mAudioManager.isAudioFocusExclusive()).thenReturn(false);
when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer);
@@ -214,8 +226,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
private void initAttentionHelper(TestableFlagResolver flagResolver) {
mAttentionHelper = new NotificationAttentionHelper(getContext(), mock(LightsManager.class),
- mAccessibilityManager, getContext().getPackageManager(), mUserManager, mUsageStats,
- mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+ mAccessibilityManager, mPackageManager, mUserManager, mUsageStats,
+ mService.mNotificationManagerPrivate, mock(ZenModeHelper.class), flagResolver);
+ mAttentionHelper.onSystemReady();
mAttentionHelper.setVibratorHelper(spy(new VibratorHelper(getContext())));
mAttentionHelper.setAudioManager(mAudioManager);
mAttentionHelper.setSystemReady(true);
@@ -226,6 +239,29 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
mAttentionHelper.setScreenOn(false);
mAttentionHelper.setInCallStateOffHook(false);
mAttentionHelper.mNotificationPulseEnabled = true;
+
+ if (Flags.crossAppPoliteNotifications()) {
+ // Capture BroadcastReceiver for avalanche triggers
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(getContext(), atLeastOnce()).registerReceiverAsUser(
+ broadcastReceiverCaptor.capture(),
+ any(), intentFilterCaptor.capture(), any(), any());
+ List<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues();
+ List<IntentFilter> intentFilters = intentFilterCaptor.getAllValues();
+
+ assertThat(broadcastReceivers.size()).isAtLeast(1);
+ assertThat(intentFilters.size()).isAtLeast(1);
+ for (int i = 0; i < intentFilters.size(); i++) {
+ final IntentFilter filter = intentFilters.get(i);
+ if (filter.hasAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+ mAvalancheBroadcastReceiver = broadcastReceivers.get(i);
+ }
+ }
+ assertThat(mAvalancheBroadcastReceiver).isNotNull();
+ }
}
//
@@ -2040,7 +2076,7 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
@Test
- public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
+ public void testBeepVolume_politeNotif_AvalancheStrategy() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2048,6 +2084,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
NotificationRecord r = getBeepyNotification();
// set up internal state
@@ -2078,7 +2119,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
@Test
- public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+ public void testBeepVolume_politeNotif_AvalancheStrategy_ChannelHasUserSound()
+ throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2086,6 +2128,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
initAttentionHelper(flagResolver);
+ // Trigger avalanche trigger intent
+ final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", false);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+
NotificationRecord r = getBeepyNotification();
// set up internal state
@@ -2364,6 +2411,82 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
+ @Test
+ public void testAvalancheStrategyTriggers() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ final int avalancheTimeoutMs = 100;
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT, avalancheTimeoutMs);
+ initAttentionHelper(flagResolver);
+
+ // Trigger avalanche trigger intents
+ for (String intentAction
+ : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ // Set the action and extras to trigger the avalanche strategy
+ Intent intent = new Intent(intentAction);
+ Pair<String, Boolean> extras =
+ NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+ .get(intentAction);
+ if (extras != null) {
+ intent.putExtra(extras.first, extras.second);
+ }
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isTrue();
+
+ // Wait for avalanche timeout
+ Thread.sleep(avalancheTimeoutMs + 1);
+
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+
+ @Test
+ public void testAvalancheStrategyTriggers_disabledExtras() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ initAttentionHelper(flagResolver);
+
+ for (String intentAction
+ : NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_INTENTS) {
+ Intent intent = new Intent(intentAction);
+ Pair<String, Boolean> extras =
+ NotificationAttentionHelper.NOTIFICATION_AVALANCHE_TRIGGER_EXTRAS
+ .get(intentAction);
+ // Test only for intents with extras
+ if (extras != null) {
+ // Set the action extras to NOT trigger the avalanche strategy
+ intent.putExtra(extras.first, !extras.second);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+ }
+
+ @Test
+ public void testAvalancheStrategyTriggers_nonAvalancheIntents() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ initAttentionHelper(flagResolver);
+
+ // Broadcast intents that are not avalanche triggers
+ final Set<String> notAvalancheTriggerIntents = Set.of(
+ Intent.ACTION_USER_ADDED,
+ Intent.ACTION_SCREEN_ON,
+ Intent.ACTION_POWER_CONNECTED
+ );
+ for (String intentAction : notAvalancheTriggerIntents) {
+ Intent intent = new Intent(intentAction);
+ mAvalancheBroadcastReceiver.onReceive(getContext(), intent);
+ // Check that avalanche strategy is inactive
+ assertThat(mAttentionHelper.getPolitenessStrategy().isActive()).isFalse();
+ }
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 0e20daf2c0f1..99d5a6d9118a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -143,12 +143,13 @@ public class ZenAdaptersTest extends UiServiceTestCase {
Policy.policyState(false, true), 0);
ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
- assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
Policy notAllowed = new Policy(0, 0, 0, 0,
Policy.policyState(false, false), 0);
ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
- assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -158,11 +159,12 @@ public class ZenAdaptersTest extends UiServiceTestCase {
Policy.policyState(false, true), 0);
ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
- assertThat(zenPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
Policy notAllowed = new Policy(0, 0, 0, 0,
Policy.policyState(false, false), 0);
ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
- assertThat(zenPolicyNotAllowed.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicyNotAllowed.getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_UNSET);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index e523e79f6370..539bb37419f1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -284,7 +284,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
actual.getPriorityConversationSenders());
assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
- assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels());
+ assertEquals(expected.getPriorityChannelsAllowed(), actual.getPriorityChannelsAllowed());
}
@Test
@@ -716,7 +716,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(policy.getPriorityCategorySystem(), fromXml.getPriorityCategorySystem());
assertEquals(policy.getPriorityCategoryReminders(), fromXml.getPriorityCategoryReminders());
assertEquals(policy.getPriorityCategoryEvents(), fromXml.getPriorityCategoryEvents());
- assertEquals(policy.getPriorityChannels(), fromXml.getPriorityChannels());
+ assertEquals(policy.getPriorityChannelsAllowed(), fromXml.getPriorityChannelsAllowed());
assertEquals(policy.getVisualEffectFullScreenIntent(),
fromXml.getVisualEffectFullScreenIntent());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 25ad7dbac30c..227265aa0a5b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -4075,7 +4075,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
@@ -4339,7 +4340,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
- assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(rule.getZenPolicy().getPriorityChannelsAllowed()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.canBeUpdatedByApp()).isFalse();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 57e11328d5e1..3a88294a0fa1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -218,7 +218,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// unset applied, channels setting keeps its state
channelsPriority.apply(unset);
- assertThat(channelsPriority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(channelsPriority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
@@ -234,7 +234,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// priority channels (less strict state) cannot override a setting that sets it to none
none.apply(priority);
- assertThat(none.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(none.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -250,7 +250,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// applying a policy with channelType=none overrides priority setting
priority.apply(none);
- assertThat(priority.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(priority.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -265,7 +265,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
// applying a policy with a set channel type actually goes through
unset.apply(priority);
- assertThat(unset.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(unset.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
@@ -379,7 +379,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
}
@Test
@@ -696,7 +696,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
builder.allowPriorityChannels(true);
ZenPolicy policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_UNSET);
assertThat(policy.toString().contains("allowChannels")).isFalse();
}
@@ -708,12 +708,12 @@ public class ZenPolicyTest extends UiServiceTestCase {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
builder.allowPriorityChannels(true);
ZenPolicy policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
// disallow priority channels
builder.allowPriorityChannels(false);
policy = builder.build();
- assertThat(policy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(policy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_DISALLOW);
}
@Test
@@ -734,7 +734,7 @@ public class ZenPolicyTest extends UiServiceTestCase {
assertThat(newPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW);
assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
- assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(newPolicy.getPriorityChannelsAllowed()).isEqualTo(ZenPolicy.STATE_ALLOW);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 7c2f7eedff9d..c8abd8d7297e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -166,6 +166,7 @@ class TestPhoneWindowManager {
@Mock
private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker;
+ @Mock private WindowWakeUpPolicy mWindowWakeUpPolicy;
@Mock private IBinder mInputToken;
@Mock private IBinder mImeTargetWindowToken;
@@ -230,6 +231,10 @@ class TestPhoneWindowManager {
TalkbackShortcutController getTalkbackShortcutController() {
return new TestTalkbackShortcutController(mContext);
}
+
+ WindowWakeUpPolicy getWindowWakeUpPolicy() {
+ return mWindowWakeUpPolicy;
+ }
}
TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
@@ -620,7 +625,8 @@ class TestPhoneWindowManager {
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
- verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
+ verify(mWindowWakeUpPolicy)
+ .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
}
void assertNoPowerSleep() {
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 2a89b02482b3..31d6fa3e91f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3722,6 +3722,68 @@ public class ActivityRecordTests extends WindowTestsBase {
assertFalse(ar.moveFocusableActivityToTop("test"));
}
+ @Test
+ public void testPauseConfigDispatch() throws RemoteException {
+ final Task task = new TaskBuilder(mSupervisor)
+ .setDisplay(mDisplayContent).setCreateActivity(true).build();
+ final ActivityRecord activity = task.getTopNonFinishingActivity();
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
+ TYPE_BASE_APPLICATION);
+ attrs.setTitle("AppWindow");
+ final TestWindowState appWindow = createWindowState(attrs, activity);
+ activity.addWindow(appWindow);
+
+ clearInvocations(mClientLifecycleManager);
+ clearInvocations(activity);
+
+ Configuration ro = activity.getRequestedOverrideConfiguration();
+ ro.windowConfiguration.setBounds(new Rect(20, 0, 120, 200));
+ activity.onRequestedOverrideConfigurationChanged(ro);
+ activity.ensureActivityConfiguration();
+ mWm.mRoot.performSurfacePlacement();
+
+ // policy will center the bounds, so just check for matching size here.
+ assertEquals(100, activity.getWindowConfiguration().getBounds().width());
+ assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+ // No scheduled transactions since it asked for a restart.
+ verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+ verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+ assertTrue(appWindow.mResizeReported);
+
+ // act like everything drew and went idle
+ appWindow.mResizeReported = false;
+ makeLastConfigReportedToClient(appWindow, true);
+
+ // Now pause dispatch and try to resize
+ activity.pauseConfigurationDispatch();
+
+ ro.windowConfiguration.setBounds(new Rect(20, 0, 150, 200));
+ activity.onRequestedOverrideConfigurationChanged(ro);
+ activity.ensureActivityConfiguration();
+ mWm.mRoot.performSurfacePlacement();
+
+ // Activity should get new config (core-side)
+ assertEquals(130, activity.getWindowConfiguration().getBounds().width());
+ // But windows should not get new config.
+ assertEquals(100, appWindow.getWindowConfiguration().getBounds().width());
+ // The client shouldn't receive any changes
+ verify(mClientLifecycleManager, times(1)).scheduleTransaction(any());
+ // and lastReported shouldn't be set.
+ verify(activity, times(1)).setLastReportedConfiguration(any(), any());
+ // There should be no resize reported to client.
+ assertFalse(appWindow.mResizeReported);
+
+ // Now resume dispatch
+ activity.resumeConfigurationDispatch();
+ mWm.mRoot.performSurfacePlacement();
+
+ // Windows and client should now receive updates
+ verify(activity, times(2)).setLastReportedConfiguration(any(), any());
+ verify(mClientLifecycleManager, times(2)).scheduleTransaction(any());
+ assertEquals(130, appWindow.getWindowConfiguration().getBounds().width());
+ assertTrue(appWindow.mResizeReported);
+ }
+
private ICompatCameraControlCallback getCompatCameraControlCallback() {
return new ICompatCameraControlCallback.Stub() {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6759eef2066d..0c1a9c3ab5b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -886,8 +886,6 @@ public class SizeCompatTests extends WindowTestsBase {
spyOn(mActivity.mLetterboxUiController);
doReturn(true).when(mActivity.mLetterboxUiController)
- .isSurfaceReadyToShow(any());
- doReturn(true).when(mActivity.mLetterboxUiController)
.isSurfaceVisible(any());
assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi(
@@ -4794,6 +4792,7 @@ public class SizeCompatTests extends WindowTestsBase {
new WindowManager.LayoutParams(TYPE_STATUS_BAR);
final Binder owner = new Binder();
attrs.gravity = android.view.Gravity.TOP;
+ attrs.height = STATUS_BAR_HEIGHT;
attrs.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
attrs.setFitInsetsTypes(0 /* types */);
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 7c7e562426c2..245b2c5a7d6b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -444,6 +444,10 @@ public class TaskFragmentTest extends WindowTestsBase {
// Not ready if the task is still visible when the TaskFragment becomes empty.
doReturn(true).when(task).isVisibleRequested();
assertFalse(taskFragment.isReadyToTransit());
+
+ // Ready if the mAllowTransitionWhenEmpty flag is true.
+ taskFragment.setAllowTransitionWhenEmpty(true);
+ assertTrue(taskFragment.isReadyToTransit());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 51df1d4cb14d..7d8eb90ea79c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1602,6 +1602,33 @@ public class TransitionTests extends WindowTestsBase {
}
@Test
+ public void testTransientWithParallelLaunch() {
+ final Task recentTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+ final ActivityRecord recent = new ActivityBuilder(mAtm).setTask(recentTask)
+ .setVisible(false).build();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ final Task appTask = app.getTask();
+ registerTestTransitionPlayer();
+ final TransitionController controller = mRootWindowContainer.mTransitionController;
+ final Transition transition = createTestTransition(TRANSIT_OPEN, controller);
+ transition.mParallelCollectType = Transition.PARALLEL_TYPE_RECENTS;
+ controller.moveToCollecting(transition);
+ transition.collect(recentTask);
+ transition.collect(appTask);
+ transition.setTransientLaunch(recent, appTask);
+ recentTask.moveToFront("move-recent-to-front");
+ transition.setAllReady();
+ transition.start();
+ // Assume that the app starts another activity in its task.
+ final Transition newTransition = controller.createAndStartCollecting(TRANSIT_OPEN);
+
+ assertEquals(newTransition, controller.getCollectingTransition());
+ assertTrue(controller.mWaitingTransitions.contains(transition));
+ assertTrue(controller.isTransientHide(appTask));
+ assertTrue(controller.isTransientVisible(appTask));
+ }
+
+ @Test
public void testNotReadyPushPop() {
final TransitionController controller = new TestTransitionController(mAtm);
controller.setSyncEngine(mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
index d2552718f218..e9ece5dbdcc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTransactionTests.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -23,6 +26,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.times;
import android.content.Intent;
import android.platform.test.annotations.Presubmit;
@@ -35,6 +39,9 @@ import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Test class for {@link WindowContainerTransaction}.
*
@@ -45,7 +52,6 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(WindowTestRunner.class)
public class WindowContainerTransactionTests extends WindowTestsBase {
-
@Test
public void testRemoveTask() {
final Task rootTask = createTask(mDisplayContent);
@@ -72,6 +78,123 @@ public class WindowContainerTransactionTests extends WindowTestsBase {
verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask);
}
+ @Test
+ public void testDesktopMode_tasksAreBroughtToFront() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 4;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ // Bring home to front of the tasks
+ desktopOrganizer.bringHomeToFront();
+
+ // Bring tasks in front of the home
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ desktopOrganizer.bringDesktopTasksToFront(wct);
+ applyTransaction(wct);
+
+ // Verify tasks are resumed and in correct z-order
+ verify(mRootWindowContainer, times(2)).ensureActivitiesVisible();
+ for (int i = 0; i < numberOfTasks - 1; i++) {
+ assertTrue(tda.mChildren
+ .indexOf(desktopOrganizer.mTasks.get(i).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask()));
+ }
+ }
+
+ @Test
+ public void testDesktopMode_moveTaskToDesktop() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 4;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ // Bring home to front of the tasks
+ desktopOrganizer.bringHomeToFront();
+
+ // Bring tasks in front of the home and newly moved task to on top of them
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ desktopOrganizer.bringDesktopTasksToFront(wct);
+ desktopOrganizer.addMoveToDesktopChanges(wct, task, true);
+ wct.setBounds(task.getTaskInfo().token, desktopOrganizer.getDefaultDesktopTaskBounds());
+ applyTransaction(wct);
+
+ // Verify tasks are resumed
+ verify(mRootWindowContainer, times(2)).ensureActivitiesVisible();
+
+ // Tasks are in correct z-order
+ for (int i = 0; i < numberOfTasks - 1; i++) {
+ assertTrue(tda.mChildren
+ .indexOf(desktopOrganizer.mTasks.get(i).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(i + 1).getRootTask()));
+ }
+ // New task is on top of other tasks
+ assertTrue(tda.mChildren
+ .indexOf(desktopOrganizer.mTasks.get(3).getRootTask())
+ < tda.mChildren.indexOf(task));
+
+ // New task is in freeform and has specified bounds
+ assertEquals(WINDOWING_MODE_FREEFORM, task.getWindowingMode());
+ assertEquals(desktopOrganizer.getDefaultDesktopTaskBounds(), task.getBounds());
+ }
+
+
+ @Test
+ public void testDesktopMode_moveTaskToFullscreen() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 4;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ Task taskToMove = desktopOrganizer.mTasks.get(numberOfTasks - 1);
+
+ // Bring tasks in front of the home and newly moved task to on top of them
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ desktopOrganizer.addMoveToFullscreen(wct, taskToMove, false);
+ applyTransaction(wct);
+
+ // New task is in freeform
+ assertEquals(WINDOWING_MODE_FULLSCREEN, taskToMove.getWindowingMode());
+ }
+
+ @Test
+ public void testDesktopMode_moveTaskToFront() {
+ final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+ TaskDisplayArea tda = desktopOrganizer.mDefaultTDA;
+ List<ActivityRecord> activityRecords = new ArrayList<>();
+ int numberOfTasks = 5;
+ desktopOrganizer.createFreeformTasksWithActivities(desktopOrganizer,
+ activityRecords, numberOfTasks);
+
+ // Bring task 2 on top of other tasks
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(desktopOrganizer.mTasks.get(2).getTaskInfo().token, true /* onTop */);
+ applyTransaction(wct);
+
+ // Tasks are in correct z-order
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(0).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask()));
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(1).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask()));
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(3).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask()));
+ assertTrue(tda.mChildren.indexOf(desktopOrganizer.mTasks.get(4).getRootTask())
+ < tda.mChildren.indexOf(desktopOrganizer.mTasks.get(2).getRootTask()));
+ }
+
private Task createTask(int taskId) {
return new Task.Builder(mAtm)
.setTaskId(taskId)
@@ -87,3 +210,4 @@ public class WindowContainerTransactionTests extends WindowTestsBase {
}
}
}
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index fe9d83776ad9..06afa381c9c0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -1068,7 +1068,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
invocationOnMock.callRealMethod();
return null;
}).when(surface).lockCanvas(any());
- mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+ mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
waitUntilHandlersIdle();
try {
verify(surface).lockCanvas(any());
@@ -1076,14 +1076,9 @@ public class WindowManagerServiceTests extends WindowTestsBase {
clearInvocations(surface);
// Invalidate and redraw.
mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
- mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+ mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId);
// Turn off magnification to release surface.
mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
- if (!com.android.window.flags.Flags.drawMagnifierBorderOutsideWmlock()) {
- verify(surface).release();
- assertTrue(lockCanvasInWmLock[0]);
- return;
- }
waitUntilHandlersIdle();
// lockCanvas must not be called after releasing.
verify(surface, never()).lockCanvas(any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a0bafb64090f..be837441ef94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -116,6 +116,7 @@ import android.window.StartingWindowRemovalInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
@@ -1899,12 +1900,14 @@ class WindowTestsBase extends SystemServiceTestsBase {
final int mDesktopModeDefaultWidthDp = 840;
final int mDesktopModeDefaultHeightDp = 630;
final int mDesktopDensity = 284;
+ final int mOverrideDensity = 285;
final ActivityTaskManagerService mService;
final TaskDisplayArea mDefaultTDA;
List<Task> mTasks;
final DisplayContent mDisplay;
Rect mStableBounds;
+ Task mHomeTask;
TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) {
mService = service;
@@ -1913,8 +1916,8 @@ class WindowTestsBase extends SystemServiceTestsBase {
mService.mTaskOrganizerController.registerTaskOrganizer(this);
mTasks = new ArrayList<>();
mStableBounds = display.getBounds();
+ mHomeTask = mDefaultTDA.getRootHomeTask();
}
-
TestDesktopOrganizer(ActivityTaskManagerService service) {
this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay());
}
@@ -1929,8 +1932,10 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
public Rect getDefaultDesktopTaskBounds() {
- int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f);
- int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f);
+ int width = (int) (mDesktopModeDefaultWidthDp
+ * (mOverrideDensity / mDesktopDensity) + 0.5f);
+ int height = (int) (mDesktopModeDefaultHeightDp
+ * (mOverrideDensity / mDesktopDensity) + 0.5f);
Rect outBounds = new Rect();
outBounds.set(0, 0, width, height);
@@ -1942,8 +1947,69 @@ class WindowTestsBase extends SystemServiceTestsBase {
return outBounds;
}
+ public void createFreeformTasksWithActivities(TestDesktopOrganizer desktopOrganizer,
+ List<ActivityRecord> activityRecords, int numberOfTasks) {
+ for (int i = 0; i < numberOfTasks; i++) {
+ Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds());
+ bounds.offset(20 * i, 20 * i);
+ desktopOrganizer.createTask(bounds);
+ }
+
+ for (int i = 0; i < numberOfTasks; i++) {
+ activityRecords.add(new TaskBuilder(mService.mTaskSupervisor)
+ .setParentTask(desktopOrganizer.mTasks.get(i))
+ .setCreateActivity(true)
+ .build()
+ .getTopMostActivity());
+ }
+
+ for (int i = 0; i < numberOfTasks; i++) {
+ activityRecords.get(i).setVisibleRequested(true);
+ }
+
+ for (int i = 0; i < numberOfTasks; i++) {
+ assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask());
+ }
+ }
+
+ public void bringHomeToFront() {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mHomeTask.getTaskInfo().token, true /* onTop */);
+ applyTransaction(wct);
+ }
+
+ public void bringDesktopTasksToFront(WindowContainerTransaction wct) {
+ for (Task task: mTasks) {
+ wct.reorder(task.getTaskInfo().token, true /* onTop */);
+ }
+ }
+
+ public void addMoveToDesktopChanges(WindowContainerTransaction wct, Task task,
+ boolean overrideDensity) {
+ wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FREEFORM);
+ wct.reorder(task.getTaskInfo().token, true /* onTop */);
+ if (overrideDensity) {
+ wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity);
+ }
+ }
+
+ public void addMoveToFullscreen(WindowContainerTransaction wct, Task task,
+ boolean overrideDensity) {
+ wct.setWindowingMode(task.getTaskInfo().token, WINDOWING_MODE_FULLSCREEN);
+ wct.setBounds(task.getTaskInfo().token, new Rect());
+ if (overrideDensity) {
+ wct.setDensityDpi(task.getTaskInfo().token, mOverrideDensity);
+ }
+ }
+
+ private void applyTransaction(@androidx.annotation.NonNull WindowContainerTransaction wct) {
+ if (!wct.isEmpty()) {
+ mService.mWindowOrganizerController.applyTransaction(wct);
+ }
+ }
}
+
static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
return createTestWindowToken(type, dc, false /* persistOnEmpty */);
}
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
index fe699af86f1d..a14078697c71 100644
--- a/telecomm/java/android/telecom/CallControl.java
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -21,7 +21,6 @@ import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
@@ -31,7 +30,6 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.text.TextUtils;
-import com.android.internal.telecom.ClientTransactionalServiceRepository;
import com.android.internal.telecom.ICallControl;
import com.android.server.telecom.flags.Flags;
@@ -52,20 +50,13 @@ import java.util.concurrent.Executor;
@SuppressLint("NotCloseable")
public final class CallControl {
private static final String TAG = CallControl.class.getSimpleName();
- private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
private final String mCallId;
private final ICallControl mServerInterface;
- private final PhoneAccountHandle mPhoneAccountHandle;
- private final ClientTransactionalServiceRepository mRepository;
/** @hide */
- public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
- @NonNull ClientTransactionalServiceRepository repository,
- @NonNull PhoneAccountHandle pah) {
+ public CallControl(@NonNull String callId, @NonNull ICallControl serverInterface) {
mCallId = callId;
mServerInterface = serverInterface;
- mRepository = repository;
- mPhoneAccountHandle = pah;
}
/**
@@ -97,16 +88,14 @@ public final class CallControl {
*/
public void setActive(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.setActive(mCallId,
- new CallControlResultReceiver("setActive", executor, callback));
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.setActive(mCallId,
+ new CallControlResultReceiver("setActive", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -134,16 +123,12 @@ public final class CallControl {
validateVideoState(videoState);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.answer(videoState, mCallId,
- new CallControlResultReceiver("answer", executor, callback));
+ try {
+ mServerInterface.answer(videoState, mCallId,
+ new CallControlResultReceiver("answer", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -165,16 +150,14 @@ public final class CallControl {
*/
public void setInactive(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.setInactive(mCallId,
- new CallControlResultReceiver("setInactive", executor, callback));
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.setInactive(mCallId,
+ new CallControlResultReceiver("setInactive", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -213,15 +196,11 @@ public final class CallControl {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
validateDisconnectCause(disconnectCause);
- if (mServerInterface != null) {
- try {
- mServerInterface.disconnect(mCallId, disconnectCause,
- new CallControlResultReceiver("disconnect", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.disconnect(mCallId, disconnectCause,
+ new CallControlResultReceiver("disconnect", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -245,15 +224,13 @@ public final class CallControl {
*/
public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
- if (mServerInterface != null) {
- try {
- mServerInterface.startCallStreaming(mCallId,
- new CallControlResultReceiver("startCallStreaming", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ try {
+ mServerInterface.startCallStreaming(mCallId,
+ new CallControlResultReceiver("startCallStreaming", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -281,15 +258,11 @@ public final class CallControl {
Objects.requireNonNull(callEndpoint);
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.requestCallEndpointChange(callEndpoint,
- new CallControlResultReceiver("endpointChange", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.requestCallEndpointChange(callEndpoint,
+ new CallControlResultReceiver("requestCallEndpointChange", executor, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -313,20 +286,16 @@ public final class CallControl {
* passed that details why the operation failed.
*/
@FlaggedApi(Flags.FLAG_SET_MUTE_STATE)
- public void setMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
+ public void requestMuteState(boolean isMuted, @CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Void, CallException> callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
- if (mServerInterface != null) {
- try {
- mServerInterface.setMuteState(isMuted,
- new CallControlResultReceiver("setMuteState", executor, callback));
+ try {
+ mServerInterface.setMuteState(isMuted,
+ new CallControlResultReceiver("requestMuteState", executor, callback));
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -352,14 +321,10 @@ public final class CallControl {
public void sendEvent(@NonNull String event, @NonNull Bundle extras) {
Objects.requireNonNull(event);
Objects.requireNonNull(extras);
- if (mServerInterface != null) {
- try {
- mServerInterface.sendEvent(mCallId, event, extras);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
- } else {
- throw new IllegalStateException(INTERFACE_ERROR_MSG);
+ try {
+ mServerInterface.sendEvent(mCallId, event, extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index a089f5c9d641..63db29713825 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -580,6 +580,9 @@ public final class PhoneAccount implements Parcelable {
mExtras = phoneAccount.getExtras();
mGroupId = phoneAccount.getGroupId();
mSupportedAudioRoutes = phoneAccount.getSupportedAudioRoutes();
+ if (phoneAccount.hasSimultaneousCallingRestriction()) {
+ mSimultaneousCallingRestriction = phoneAccount.getSimultaneousCallingRestriction();
+ }
}
/**
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e81f48280e46..2c6e1e48b57f 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1360,7 +1360,7 @@ public class TelecomManager {
/**
* Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
* calls. The returned list includes those accounts which have been explicitly enabled by
- * the user or other users visible to the user.
+ * the user or enabled by other users but visible to the user.
*
* @see #EXTRA_PHONE_ACCOUNT_HANDLE
* @return A list of {@code PhoneAccountHandle} objects.
@@ -1486,7 +1486,7 @@ public class TelecomManager {
return service.getCallCapablePhoneAccounts(includeDisabledAccounts,
mContext.getOpPackageName(), mContext.getAttributionTag(), true).getList();
} catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
index 71e9184b7c54..467e89c78810 100644
--- a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -208,8 +208,7 @@ public class ClientTransactionalServiceWrapper {
if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
// create the interface object that the client will interact with
- CallControl control = new CallControl(callId, callControl, mRepository,
- mPhoneAccountHandle);
+ CallControl control = new CallControl(callId, callControl);
// give the client the object via the OR that was passed into addCall
pendingControl.onResult(control);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1badf674c8ce..a73c46b12c53 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9430,16 +9430,6 @@ public class CarrierConfigManager {
"missed_incoming_call_sms_originator_string_array";
/**
- * String array of Apn Type configurations.
- * The entries should be of form "APN_TYPE_NAME:priority".
- * priority is an integer that is sorted from highest to lowest.
- * example: cbs:5
- *
- * @hide
- */
- public static final String KEY_APN_PRIORITY_STRING_ARRAY = "apn_priority_string_array";
-
- /**
* Network capability priority for determine the satisfy order in telephony. The priority is
* from the lowest 0 to the highest 100. The long-lived network shall have the lowest priority.
* This allows other short-lived requests like MMS requests to be established. Emergency request
@@ -10755,17 +10745,14 @@ public class CarrierConfigManager {
TimeUnit.DAYS.toMillis(1));
sDefaults.putStringArray(KEY_MISSED_INCOMING_CALL_SMS_ORIGINATOR_STRING_ARRAY,
new String[0]);
- sDefaults.putStringArray(KEY_APN_PRIORITY_STRING_ARRAY, new String[] {
- "enterprise:0", "default:1", "mms:2", "supl:2", "dun:2", "hipri:3", "fota:2",
- "ims:2", "cbs:2", "ia:2", "emergency:2", "mcx:3", "xcap:3"
- });
// Do not modify the priority unless you know what you are doing. This will have significant
// impacts on the order of data network setup.
sDefaults.putStringArray(
KEY_TELEPHONY_NETWORK_CAPABILITY_PRIORITIES_STRING_ARRAY, new String[] {
"eims:90", "supl:80", "mms:70", "xcap:70", "cbs:50", "mcx:50", "fota:50",
- "ims:40", "dun:30", "enterprise:20", "internet:20"
+ "ims:40", "rcs:40", "dun:30", "enterprise:20", "internet:20",
+ "prioritize_bandwidth:20", "prioritize_latency:20"
});
sDefaults.putStringArray(
KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
@@ -10777,9 +10764,10 @@ public class CarrierConfigManager {
// registration state changes) retry can still happen.
"permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
+ "-3|65543|65547|2252|2253|2254, retry_interval=2500",
- "capabilities=mms|supl|cbs, retry_interval=2000",
- "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
- + "5000|10000|15000|20000|40000|60000|120000|240000|"
+ "capabilities=mms|supl|cbs|rcs, retry_interval=2000",
+ "capabilities=internet|enterprise|dun|ims|fota|xcap|mcx|"
+ + "prioritize_bandwidth|prioritize_latency, retry_interval="
+ + "2500|3000|5000|10000|15000|20000|40000|60000|120000|240000|"
+ "600000|1200000|1800000, maximum_retries=20"
});
sDefaults.putStringArray(
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index cbd552454642..c1ceaef64d5d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -25,6 +25,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.Manifest;
import android.annotation.BytesLong;
import android.annotation.CallbackExecutor;
+import android.annotation.CurrentTimeMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.LongDef;
@@ -18714,51 +18715,93 @@ public class TelephonyManager {
* call diagnostic data
* @hide
*/
- public static class EmergencyCallDiagnosticParams {
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+ public static final class EmergencyCallDiagnosticParams {
+ public static final class Builder {
+ private boolean mCollectTelecomDumpSys;
+ private boolean mCollectTelephonyDumpsys;
+
+ // If this is set to a value other than -1L, then the logcat collection is enabled.
+ // Logcat lines with this time or greater are collected how much is collected is
+ // dependent on internal implementation. Time represented as milliseconds since boot.
+ private long mLogcatStartTimeMillis = sUnsetLogcatStartTime;
+
+ /**
+ * Allows enabling of telecom dumpsys collection.
+ * @param collectTelecomDumpsys Determines whether telecom dumpsys should be collected.
+ * @return Builder instance corresponding to the configured call diagnostic params.
+ */
+ public @NonNull Builder setTelecomDumpSysCollectionEnabled(
+ boolean collectTelecomDumpsys) {
+ mCollectTelecomDumpSys = collectTelecomDumpsys;
+ return this;
+ }
+
+ /**
+ * Allows enabling of telephony dumpsys collection.
+ * @param collectTelephonyDumpsys Determines if telephony dumpsys should be collected.
+ * @return Builder instance corresponding to the configured call diagnostic params.
+ */
+ public @NonNull Builder setTelephonyDumpSysCollectionEnabled(
+ boolean collectTelephonyDumpsys) {
+ mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+ return this;
+ }
+
+ /**
+ * Allows enabling of logcat (system,radio) collection.
+ * @param startTimeMillis Enables logcat collection as of the indicated timestamp.
+ * @return Builder instance corresponding to the configured call diagnostic params.
+ */
+ public @NonNull Builder setLogcatCollectionStartTimeMillis(
+ @CurrentTimeMillisLong long startTimeMillis) {
+ mLogcatStartTimeMillis = startTimeMillis;
+ return this;
+ }
- private boolean mCollectTelecomDumpSys;
- private boolean mCollectTelephonyDumpsys;
- private boolean mCollectLogcat;
+ /**
+ * Build the EmergencyCallDiagnosticParams from the provided Builder config.
+ * @return {@link EmergencyCallDiagnosticParams} instance from provided builder.
+ */
+ public @NonNull EmergencyCallDiagnosticParams build() {
+ return new EmergencyCallDiagnosticParams(mCollectTelecomDumpSys,
+ mCollectTelephonyDumpsys, mLogcatStartTimeMillis);
+ }
+ }
- //logcat lines with this time or greater are collected
- //how much is collected is dependent on internal implementation.
- //Time represented as milliseconds since January 1, 1970 UTC
+ private boolean mCollectTelecomDumpSys;
+ private boolean mCollectTelephonyDumpsys;
+ private boolean mCollectLogcat;
private long mLogcatStartTimeMillis;
+ private static long sUnsetLogcatStartTime = -1L;
- public boolean isTelecomDumpSysCollectionEnabled() {
- return mCollectTelecomDumpSys;
+ private EmergencyCallDiagnosticParams(boolean collectTelecomDumpSys,
+ boolean collectTelephonyDumpsys, long logcatStartTimeMillis) {
+ mCollectTelecomDumpSys = collectTelecomDumpSys;
+ mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+ mLogcatStartTimeMillis = logcatStartTimeMillis;
+ mCollectLogcat = logcatStartTimeMillis != sUnsetLogcatStartTime;
}
- public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) {
- mCollectTelecomDumpSys = collectTelecomDumpSys;
+ public boolean isTelecomDumpSysCollectionEnabled() {
+ return mCollectTelecomDumpSys;
}
public boolean isTelephonyDumpSysCollectionEnabled() {
return mCollectTelephonyDumpsys;
}
- public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) {
- mCollectTelephonyDumpsys = collectTelephonyDumpsys;
- }
-
public boolean isLogcatCollectionEnabled() {
return mCollectLogcat;
}
- public long getLogcatStartTime()
+ public long getLogcatCollectionStartTimeMillis()
{
return mLogcatStartTimeMillis;
}
- public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) {
- mCollectLogcat = collectLogcat;
- if(mCollectLogcat)
- {
- mLogcatStartTimeMillis = startTimeMillis;
- }
- }
-
@Override
public String toString() {
return "EmergencyCallDiagnosticParams{" +
@@ -18774,11 +18817,12 @@ public class TelephonyManager {
* Request telephony to persist state for debugging emergency call failures.
*
* @param dropboxTag Tag to use when persisting data to dropbox service.
- *
- * @see params Parameters controlling what is collected
+ * @param params Parameters controlling what is collected.
*
* @hide
*/
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
@RequiresPermission(android.Manifest.permission.DUMP)
public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
@NonNull EmergencyCallDiagnosticParams params) {
@@ -18791,7 +18835,7 @@ public class TelephonyManager {
if (telephony != null) {
telephony.persistEmergencyCallDiagnosticData(dropboxTag,
params.isLogcatCollectionEnabled(),
- params.getLogcatStartTime(),
+ params.getLogcatCollectionStartTimeMillis(),
params.isTelecomDumpSysCollectionEnabled(),
params.isTelephonyDumpSysCollectionEnabled());
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 70047a6feb9c..a1ac477d3519 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -34,7 +34,6 @@ import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.ServiceSpecificException;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyFrameworkInitializer;
@@ -336,6 +335,12 @@ public final class SatelliteManager {
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_RESULT_MODEM_BUSY = 22;
+ /**
+ * Telephony process is not currently available or satellite is not supported.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23;
+
/** @hide */
@IntDef(prefix = {"SATELLITE_RESULT_"}, value = {
SATELLITE_RESULT_SUCCESS,
@@ -360,7 +365,8 @@ public final class SatelliteManager {
SATELLITE_RESULT_NOT_AUTHORIZED,
SATELLITE_RESULT_NOT_SUPPORTED,
SATELLITE_RESULT_REQUEST_IN_PROGRESS,
- SATELLITE_RESULT_MODEM_BUSY
+ SATELLITE_RESULT_MODEM_BUSY,
+ SATELLITE_RESULT_ILLEGAL_STATE
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteResult {}
@@ -510,7 +516,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
Rlog.e(TAG, "requestSatelliteEnabled() RemoteException: ", ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -526,7 +532,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -561,11 +566,12 @@ public final class SatelliteManager {
};
telephony.requestIsSatelliteEnabled(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteEnabled() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -581,7 +587,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -616,11 +621,12 @@ public final class SatelliteManager {
};
telephony.requestIsDemoModeEnabled(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsDemoModeEnabled() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -639,8 +645,6 @@ public final class SatelliteManager {
* service is supported on the device and {@code false} otherwise.
* If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
- *
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
@@ -674,11 +678,12 @@ public final class SatelliteManager {
};
telephony.requestIsSatelliteSupported(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteSupported() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -693,7 +698,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -729,11 +733,12 @@ public final class SatelliteManager {
};
telephony.requestSatelliteCapabilities(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestSatelliteCapabilities() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -959,7 +964,6 @@ public final class SatelliteManager {
* @param callback The callback to notify of satellite transmission updates.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1009,11 +1013,12 @@ public final class SatelliteManager {
telephony.startSatelliteTransmissionUpdates(mSubId, errorCallback,
internalCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("startSatelliteTransmissionUpdates() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1029,7 +1034,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1063,11 +1067,12 @@ public final class SatelliteManager {
() -> resultListener.accept(SATELLITE_RESULT_INVALID_ARGUMENTS)));
}
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("stopSatelliteTransmissionUpdates() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1112,11 +1117,12 @@ public final class SatelliteManager {
cancelRemote = telephony.provisionSatelliteService(mSubId, token, provisionData,
errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("provisionSatelliteService() RemoteException=" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
if (cancellationSignal != null) {
cancellationSignal.setRemote(cancelRemote);
@@ -1138,7 +1144,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1161,11 +1166,12 @@ public final class SatelliteManager {
};
telephony.deprovisionSatelliteService(mSubId, token, errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("deprovisionSatelliteService() RemoteException=" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1208,7 +1214,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteProvisionStateChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -1244,7 +1250,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteProvisionStateChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1260,7 +1266,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1295,11 +1300,12 @@ public final class SatelliteManager {
};
telephony.requestIsSatelliteProvisioned(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1340,7 +1346,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteModemStateChanged() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -1376,7 +1382,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteModemStateChanged() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1436,7 +1442,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteDatagram() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -1471,7 +1477,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteDatagram() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1488,7 +1494,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1509,11 +1514,12 @@ public final class SatelliteManager {
};
telephony.pollPendingSatelliteDatagrams(mSubId, internalCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("pollPendingSatelliteDatagrams() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1541,7 +1547,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1566,11 +1571,12 @@ public final class SatelliteManager {
telephony.sendSatelliteDatagram(mSubId, datagramType, datagram,
needFullScreenPointingUI, internalCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("sendSatelliteDatagram() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1587,7 +1593,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1624,12 +1629,13 @@ public final class SatelliteManager {
telephony.requestIsSatelliteCommunicationAllowedForCurrentLocation(mSubId,
receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestIsSatelliteCommunicationAllowedForCurrentLocation() RemoteException: "
+ ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1645,7 +1651,6 @@ public final class SatelliteManager {
* will return a {@link SatelliteException} with the {@link SatelliteResult}.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1681,11 +1686,12 @@ public final class SatelliteManager {
};
telephony.requestTimeForNextSatelliteVisibility(mSubId, receiver);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestTimeForNextSatelliteVisibility() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1713,7 +1719,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("informDeviceAlignedToSatellite() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1727,7 +1733,7 @@ public final class SatelliteManager {
* <ul>
* <li>Users want to enable it.</li>
* <li>There is no satellite communication restriction, which is added by
- * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}</li>
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)}</li>
* <li>The carrier config {@link
* android.telephony.CarrierConfigManager#KEY_SATELLITE_ATTACH_SUPPORTED_BOOL} is set to
* {@code true}.</li>
@@ -1739,7 +1745,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1799,7 +1804,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1824,11 +1828,12 @@ public final class SatelliteManager {
};
telephony.addSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("addSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1842,7 +1847,6 @@ public final class SatelliteManager {
* @param resultListener Listener for the {@link SatelliteResult} result of the operation.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available.
* @throws IllegalArgumentException if the subscription is invalid.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -1867,17 +1871,18 @@ public final class SatelliteManager {
};
telephony.removeSatelliteAttachRestrictionForCarrier(subId, reason, errorCallback);
} else {
- throw new IllegalStateException("telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> resultListener.accept(SATELLITE_RESULT_ILLEGAL_STATE)));
}
} catch (RemoteException ex) {
loge("removeSatelliteAttachRestrictionForCarrier() RemoteException:" + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
/**
* Get reasons for disallowing satellite attach, as requested by
- * {@link #addSatelliteAttachRestrictionForCarrier(int, Executor, Consumer)}
+ * {@link #addSatelliteAttachRestrictionForCarrier(int, int, Executor, Consumer)}
*
* @param subId The subscription ID of the carrier.
* @return Set of reasons for disallowing satellite communication.
@@ -1910,7 +1915,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("getSatelliteAttachRestrictionReasonsForCarrier() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return new HashSet<>();
}
@@ -1932,11 +1937,12 @@ public final class SatelliteManager {
* The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
* signal strength data available.
* If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
- * {@link SatelliteException} with the {@link SatelliteResult}.
+ * {@link SatelliteException} with the {@link SatelliteResult}, or return a
+ * {@link IllegalStateException} if the Telephony process is not currently available or
+ * satellite is not supported, or return a {@link RuntimeException} when remote procedure call
+ * has failed.
*
* @throws SecurityException if the caller doesn't have required permission.
- * @throws IllegalStateException if the Telephony process is not currently available or
- * satellite is not supported.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1972,11 +1978,12 @@ public final class SatelliteManager {
};
telephony.requestNtnSignalStrength(mSubId, receiver);
} else {
- throw new IllegalStateException("Telephony service is null.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
}
} catch (RemoteException ex) {
loge("requestNtnSignalStrength() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -1997,12 +2004,11 @@ public final class SatelliteManager {
*
* @throws SecurityException if the caller doesn't have required permission.
* @throws IllegalStateException if the Telephony process is not currently available.
- * @throws SatelliteException if the callback registration operation fails.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor,
- @NonNull NtnSignalStrengthCallback callback) throws SatelliteException {
+ @NonNull NtnSignalStrengthCallback callback) {
Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
@@ -2024,12 +2030,9 @@ public final class SatelliteManager {
} else {
throw new IllegalStateException("Telephony service is null.");
}
- } catch (ServiceSpecificException ex) {
- logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode);
- throw new SatelliteException(ex.errorCode);
} catch (RemoteException ex) {
loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -2072,7 +2075,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -2113,7 +2116,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("registerForSatelliteCapabilitiesChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return SATELLITE_RESULT_REQUEST_FAILED;
}
@@ -2149,7 +2152,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("unregisterForSatelliteCapabilitiesChanged() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
}
@@ -2177,7 +2180,7 @@ public final class SatelliteManager {
}
} catch (RemoteException ex) {
loge("getAllSatellitePlmnsForCarrier() RemoteException: " + ex);
- ex.rethrowFromSystemServer();
+ ex.rethrowAsRuntimeException();
}
return new ArrayList<>();
}
diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
index b7712bd83cf6..655da740012b 100644
--- a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -16,7 +16,7 @@
package android.telephony.satellite.stub;
-import android.telephony.satellite.NtnSignalStrength;
+import android.telephony.satellite.stub.NtnSignalStrength;
/**
* Consumer pattern for a request that receives the signal strength of non-terrestrial network from
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 256a4696b763..566e51a9062a 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -24,6 +24,7 @@ import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.test.mock.MockContentResolver
import android.view.Display
@@ -72,6 +73,9 @@ class InputManagerServiceTests {
@get:Rule
val fakeSettingsProviderRule = FakeSettingsProvider.rule()!!
+ @get:Rule
+ val setFlagsRule = SetFlagsRule()
+
@Mock
private lateinit var native: NativeInputManagerService
@@ -170,6 +174,8 @@ class InputManagerServiceTests {
@Test
fun testSetVirtualMousePointerDisplayId() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
// Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
// until the native callback happens.
var countDownLatch = CountDownLatch(1)
@@ -221,6 +227,8 @@ class InputManagerServiceTests {
@Test
fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
// Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked
// until the native callback happens.
val countDownLatch = CountDownLatch(1)
@@ -246,6 +254,8 @@ class InputManagerServiceTests {
@Test
fun testSetVirtualMousePointerDisplayId_competingRequests() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
val firstRequestSyncLatch = CountDownLatch(1)
doAnswer {
firstRequestSyncLatch.countDown()
@@ -289,6 +299,8 @@ class InputManagerServiceTests {
@Test
fun onDisplayRemoved_resetAllAdditionalInputProperties() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
setVirtualMousePointerDisplayIdAndVerify(10)
localService.setPointerIconVisible(false, 10)
@@ -313,6 +325,8 @@ class InputManagerServiceTests {
@Test
fun updateAdditionalInputPropertiesForOverrideDisplay() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
setVirtualMousePointerDisplayIdAndVerify(10)
localService.setPointerIconVisible(false, 10)
@@ -341,6 +355,8 @@ class InputManagerServiceTests {
@Test
fun setAdditionalInputPropertiesBeforeOverride() {
+ setFlagsRule.disableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER)
+
localService.setPointerIconVisible(false, 10)
localService.setMousePointerAccelerationEnabled(false, 10)
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
index 5aaf30a5b3a7..14230fe4c323 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/EmbeddedWindowService.java
@@ -134,7 +134,7 @@ public class EmbeddedWindowService extends Service {
c.drawText("Remote", 250, 250, paint);
surface.unlockCanvasAndPost(c);
WindowManager wm = getSystemService(WindowManager.class);
- mInputToken = wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
+ wm.registerBatchedSurfaceControlInputReceiver(displayId, hostToken,
mSurfaceControl,
Choreographer.getInstance(), event -> {
Log.d(TAG, "onInputEvent-remote " + event);
@@ -147,11 +147,9 @@ public class EmbeddedWindowService extends Service {
@Override
public void tearDownEmbeddedSurfaceControl() {
if (mSurfaceControl != null) {
- new SurfaceControl.Transaction().remove(mSurfaceControl);
- }
- if (mInputToken != null) {
WindowManager wm = getSystemService(WindowManager.class);
- wm.unregisterSurfaceControlInputReceiver(mInputToken);
+ wm.unregisterSurfaceControlInputReceiver(mSurfaceControl);
+ new SurfaceControl.Transaction().remove(mSurfaceControl).apply();
}
}
}
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
index e5f8f47aeecd..7330ec14011b 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -50,10 +50,11 @@ public class SurfaceInputTestActivity extends Activity {
private static final String TAG = "SurfaceInputTestActivity";
private SurfaceView mLocalSurfaceView;
private SurfaceView mRemoteSurfaceView;
- private IBinder mInputToken;
private IAttachEmbeddedWindow mIAttachEmbeddedWindow;
private SurfaceControl mParentSurfaceControl;
+ private SurfaceControl mLocalSurfaceControl;
+
private final ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName className, IBinder service) {
@@ -112,30 +113,33 @@ public class SurfaceInputTestActivity extends Activity {
@Override
protected void onDestroy() {
super.onDestroy();
- getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
+ if (mLocalSurfaceControl != null) {
+ getWindowManager().unregisterSurfaceControlInputReceiver(mLocalSurfaceControl);
+ new SurfaceControl.Transaction().remove(mLocalSurfaceControl).apply();
+ }
}
private void addLocalChildSurfaceControl(AttachedSurfaceControl attachedSurfaceControl) {
- SurfaceControl surfaceControl = new SurfaceControl.Builder().setName("LocalSC")
+ mLocalSurfaceControl = new SurfaceControl.Builder().setName("LocalSC")
.setBufferSize(100, 100).build();
- attachedSurfaceControl.buildReparentTransaction(surfaceControl)
- .setVisibility(surfaceControl, true)
- .setCrop(surfaceControl, new Rect(0, 0, 100, 100))
- .setPosition(surfaceControl, 250, 1000)
- .setLayer(surfaceControl, 1).apply();
+ attachedSurfaceControl.buildReparentTransaction(mLocalSurfaceControl)
+ .setVisibility(mLocalSurfaceControl, true)
+ .setCrop(mLocalSurfaceControl, new Rect(0, 0, 100, 100))
+ .setPosition(mLocalSurfaceControl, 250, 1000)
+ .setLayer(mLocalSurfaceControl, 1).apply();
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setTextSize(20);
- Surface surface = new Surface(surfaceControl);
+ Surface surface = new Surface(mLocalSurfaceControl);
Canvas c = surface.lockCanvas(null);
c.drawColor(Color.GREEN);
c.drawText("Local SC", 0, 0, paint);
surface.unlockCanvasAndPost(c);
WindowManager wm = getSystemService(WindowManager.class);
- mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
- attachedSurfaceControl.getHostToken(), surfaceControl,
+ wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ attachedSurfaceControl.getHostToken(), mLocalSurfaceControl,
Choreographer.getInstance(), event -> {
Log.d(TAG, "onInputEvent-sc " + event);
return false;
@@ -143,8 +147,6 @@ public class SurfaceInputTestActivity extends Activity {
}
private final SurfaceHolder.Callback mLocalSurfaceViewCallback = new SurfaceHolder.Callback() {
- private IBinder mInputToken;
-
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
Paint paint = new Paint();
@@ -157,7 +159,7 @@ public class SurfaceInputTestActivity extends Activity {
holder.unlockCanvasAndPost(c);
WindowManager wm = getSystemService(WindowManager.class);
- mInputToken = wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
+ wm.registerBatchedSurfaceControlInputReceiver(getDisplayId(),
mLocalSurfaceView.getHostToken(), mLocalSurfaceView.getSurfaceControl(),
Choreographer.getInstance(), event -> {
Log.d(TAG, "onInputEvent-local " + event);
@@ -173,9 +175,8 @@ public class SurfaceInputTestActivity extends Activity {
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
- if (mInputToken != null) {
- getWindowManager().unregisterSurfaceControlInputReceiver(mInputToken);
- }
+ getWindowManager().unregisterSurfaceControlInputReceiver(
+ mLocalSurfaceView.getSurfaceControl());
}
};
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
index 1ec1d5f307e1..2f6a361e3609 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -15,42 +15,181 @@
*/
package com.android.hoststubgen.nativesubstitution;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
+
public class SystemProperties_host {
+ private static final Object sLock = new Object();
+
+ /** Active system property values */
+ @GuardedBy("sLock")
+ private static Map<String, String> sValues;
+ /** Predicate tested to determine if a given key can be read. */
+ @GuardedBy("sLock")
+ private static Predicate<String> sKeyReadablePredicate;
+ /** Predicate tested to determine if a given key can be written. */
+ @GuardedBy("sLock")
+ private static Predicate<String> sKeyWritablePredicate;
+ /** Callback to trigger when values are changed */
+ @GuardedBy("sLock")
+ private static Runnable sChangeCallback;
+
+ /**
+ * Reverse mapping that provides a way back to an original key from the
+ * {@link System#identityHashCode(Object)} of {@link String#intern}.
+ */
+ @GuardedBy("sLock")
+ private static SparseArray<String> sKeyHandles = new SparseArray<>();
+
+ public static void native_init$ravenwood(Map<String, String> values,
+ Predicate<String> keyReadablePredicate, Predicate<String> keyWritablePredicate,
+ Runnable changeCallback) {
+ synchronized (sLock) {
+ sValues = Objects.requireNonNull(values);
+ sKeyReadablePredicate = Objects.requireNonNull(keyReadablePredicate);
+ sKeyWritablePredicate = Objects.requireNonNull(keyWritablePredicate);
+ sChangeCallback = Objects.requireNonNull(changeCallback);
+ sKeyHandles.clear();
+ }
+ }
+
+ public static void native_reset$ravenwood() {
+ synchronized (sLock) {
+ sValues = null;
+ sKeyReadablePredicate = null;
+ sKeyWritablePredicate = null;
+ sChangeCallback = null;
+ sKeyHandles.clear();
+ }
+ }
+
+ public static void native_set(String key, String val) {
+ synchronized (sLock) {
+ Objects.requireNonNull(key);
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (!sKeyWritablePredicate.test(key)) {
+ throw new IllegalArgumentException(
+ "Write access to system property '" + key + "' denied via RavenwoodRule");
+ }
+ if (key.startsWith("ro.") && sValues.containsKey(key)) {
+ throw new IllegalArgumentException(
+ "System property '" + key + "' already defined once; cannot redefine");
+ }
+ if ((val == null) || val.isEmpty()) {
+ sValues.remove(key);
+ } else {
+ sValues.put(key, val);
+ }
+ sChangeCallback.run();
+ }
+ }
+
public static String native_get(String key, String def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ Objects.requireNonNull(key);
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (!sKeyReadablePredicate.test(key)) {
+ throw new IllegalArgumentException(
+ "Read access to system property '" + key + "' denied via RavenwoodRule");
+ }
+ return sValues.getOrDefault(key, def);
+ }
}
+
public static int native_get_int(String key, int def) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Integer.parseInt(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static long native_get_long(String key, long def) {
- throw new RuntimeException("Not implemented yet");
+ try {
+ return Long.parseLong(native_get(key, ""));
+ } catch (NumberFormatException ignored) {
+ return def;
+ }
}
+
public static boolean native_get_boolean(String key, boolean def) {
- throw new RuntimeException("Not implemented yet");
+ return parseBoolean(native_get(key, ""), def);
}
public static long native_find(String name) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ if (sValues.containsKey(name)) {
+ name = name.intern();
+ final int handle = System.identityHashCode(name);
+ sKeyHandles.put(handle, name);
+ return handle;
+ } else {
+ return 0;
+ }
+ }
}
+
public static String native_get(long handle) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get(sKeyHandles.get((int) handle), "");
+ }
}
+
public static int native_get_int(long handle, int def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_int(sKeyHandles.get((int) handle), def);
+ }
}
+
public static long native_get_long(long handle, long def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_long(sKeyHandles.get((int) handle), def);
+ }
}
+
public static boolean native_get_boolean(long handle, boolean def) {
- throw new RuntimeException("Not implemented yet");
- }
- public static void native_set(String key, String def) {
- throw new RuntimeException("Not implemented yet");
+ synchronized (sLock) {
+ return native_get_boolean(sKeyHandles.get((int) handle), def);
+ }
}
+
public static void native_add_change_callback() {
- throw new RuntimeException("Not implemented yet");
+ // Ignored; callback always registered via init above
}
+
public static void native_report_sysprop_change() {
- throw new RuntimeException("Not implemented yet");
+ // Report through callback always registered via init above
+ synchronized (sLock) {
+ Preconditions.requireNonNullViaRavenwoodRule(sValues);
+ sChangeCallback.run();
+ }
+ }
+
+ private static boolean parseBoolean(String val, boolean def) {
+ // Matches system/libbase/include/android-base/parsebool.h
+ if (val == null) return def;
+ switch (val) {
+ case "1":
+ case "on":
+ case "true":
+ case "y":
+ case "yes":
+ return true;
+ case "0":
+ case "false":
+ case "n":
+ case "no":
+ case "off":
+ return false;
+ default:
+ return def;
+ }
}
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
index 8ca4732f57c4..76bac9286a1f 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt
@@ -24,6 +24,7 @@ class AndroidHeuristicsFilter(
private val classes: ClassNodes,
val aidlPolicy: FilterPolicyWithReason?,
val featureFlagsPolicy: FilterPolicyWithReason?,
+ val syspropsPolicy: FilterPolicyWithReason?,
fallback: OutputFilter
) : DelegatingFilter(fallback) {
override fun getPolicyForClass(className: String): FilterPolicyWithReason {
@@ -33,6 +34,9 @@ class AndroidHeuristicsFilter(
if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
return featureFlagsPolicy
}
+ if (syspropsPolicy != null && classes.isSyspropsClass(className)) {
+ return syspropsPolicy
+ }
return super.getPolicyForClass(className)
}
}
@@ -57,3 +61,13 @@ private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean {
|| className.endsWith("/FeatureFlagsImpl")
|| className.endsWith("/FakeFeatureFlagsImpl");
}
+
+/**
+ * @return if a given class "seems like" a sysprops class.
+ */
+private fun ClassNodes.isSyspropsClass(className: String): Boolean {
+ // Matches template classes defined here:
+ // https://cs.android.com/android/platform/superproject/main/+/main:system/tools/sysprop/
+ return className.startsWith("android/sysprop/")
+ && className.endsWith("Properties")
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index d38a6e34e09f..7fdd944770c6 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,6 +64,7 @@ fun createFilterFromTextPolicyFile(
var aidlPolicy: FilterPolicyWithReason? = null
var featureFlagsPolicy: FilterPolicyWithReason? = null
+ var syspropsPolicy: FilterPolicyWithReason? = null
try {
BufferedReader(FileReader(filename)).use { reader ->
@@ -141,6 +142,14 @@ fun createFilterFromTextPolicyFile(
featureFlagsPolicy =
policy.withReason("$FILTER_REASON (feature flags)")
}
+ SpecialClass.Sysprops -> {
+ if (syspropsPolicy != null) {
+ throw ParseException(
+ "Policy for sysprops already defined")
+ }
+ syspropsPolicy =
+ policy.withReason("$FILTER_REASON (sysprops)")
+ }
}
}
}
@@ -205,10 +214,10 @@ fun createFilterFromTextPolicyFile(
}
var ret: OutputFilter = imf
- if (aidlPolicy != null || featureFlagsPolicy != null) {
+ if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) {
log.d("AndroidHeuristicsFilter enabled")
ret = AndroidHeuristicsFilter(
- classes, aidlPolicy, featureFlagsPolicy, imf)
+ classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf)
}
return ret
}
@@ -218,6 +227,7 @@ private enum class SpecialClass {
NotSpecial,
Aidl,
FeatureFlags,
+ Sysprops,
}
private fun resolveSpecialClass(className: String): SpecialClass {
@@ -227,6 +237,7 @@ private fun resolveSpecialClass(className: String): SpecialClass {
when (className.lowercase()) {
":aidl" -> return SpecialClass.Aidl
":feature_flags" -> return SpecialClass.FeatureFlags
+ ":sysprops" -> return SpecialClass.Sysprops
}
throw ParseException("Invalid special class name \"$className\"")
}